summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp4
-rw-r--r--api/api.go5
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/test-current.txt43
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/NotificationManager.java95
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java38
-rw-r--r--core/java/android/app/notification.aconfig17
-rw-r--r--core/java/android/app/supervision/ISupervisionAppService.aidl23
-rw-r--r--core/java/android/app/supervision/SupervisionAppService.java37
-rw-r--r--core/java/android/app/supervision/flags.aconfig8
-rw-r--r--core/java/android/database/sqlite/SQLiteOpenHelper.java140
-rw-r--r--core/java/android/database/sqlite/flags.aconfig9
-rw-r--r--core/java/android/hardware/contexthub/HubEndpoint.java291
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig7
-rw-r--r--core/java/android/os/IpcDataCache.java70
-rw-r--r--core/java/android/provider/Settings.java15
-rw-r--r--core/java/android/service/notification/StatusBarNotification.java63
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceValue.java103
-rw-r--r--core/java/android/text/Layout.java6
-rw-r--r--core/java/android/window/TaskFragmentOperation.java11
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig17
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig9
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java3
-rw-r--r--core/java/com/android/internal/accessibility/util/ShortcutUtils.java45
-rw-r--r--core/java/com/android/internal/jank/Cuj.java16
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java4
-rw-r--r--core/res/AndroidManifest.xml21
-rw-r--r--core/tests/coretests/src/android/app/NotificationManagerTest.java44
-rw-r--r--core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java2
-rw-r--r--core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java33
-rw-r--r--core/tests/coretests/src/android/text/LayoutTest.java49
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java112
-rw-r--r--data/etc/preinstalled-packages-platform.xml2
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java11
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/aconfig/Android.bp1
-rw-r--r--libs/WindowManager/Shell/aconfig/automotive.aconfig11
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig14
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt293
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt611
-rw-r--r--location/java/android/location/Geocoder.java7
-rw-r--r--media/jni/android_media_tv_Tuner.cpp26
-rw-r--r--media/tests/MediaRouter/Android.bp2
-rw-r--r--native/android/TEST_MAPPING4
-rw-r--r--nfc-non-updatable/flags/flags.aconfig8
-rw-r--r--packages/EasterEgg/AndroidManifest.xml6
-rw-r--r--packages/EasterEgg/res/drawable/android16_patch_adaptive.xml21
-rw-r--r--packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml245
-rw-r--r--packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml35
-rw-r--r--packages/EasterEgg/res/drawable/android16_patch_monochrome.xml113
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt2
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt31
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/Physics.kt24
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt118
-rw-r--r--packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml28
-rw-r--r--packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml35
-rw-r--r--packages/SettingsLib/CardPreference/res/values/styles_expressive.xml4
-rw-r--r--packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt10
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig10
-rw-r--r--packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java16
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java69
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig10
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java81
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/AndroidManifest.xml8
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/aconfig/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig10
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java12
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt33
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt228
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt38
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt94
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt116
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt121
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt92
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt168
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt85
-rw-r--r--packages/SystemUI/res/drawable/android16_patch_adaptive.xml21
-rw-r--r--packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml245
-rw-r--r--packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml35
-rw-r--r--packages/SystemUI/res/drawable/android16_patch_monochrome.xml113
-rw-r--r--packages/SystemUI/res/layout/qs_footer_impl.xml5
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/BuildTextView.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt50
-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/phone/PhoneStatusBarViewControllerTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt2
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java2
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java8
-rw-r--r--ravenwood/texts/ravenwood-framework-policies.txt2
-rwxr-xr-xravenwood/tools/hoststubgen/scripts/dump-jar29
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt28
-rwxr-xr-xravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh9
-rwxr-xr-xravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py20
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java13
-rw-r--r--services/core/Android.bp2
-rw-r--r--services/core/java/com/android/server/OWNERS1
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java18
-rw-r--r--services/core/java/com/android/server/ZramMaintenance.java118
-rw-r--r--services/core/java/com/android/server/crashrecovery/OWNERS1
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java67
-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.aconfig8
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java4
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudger.java28
-rw-r--r--services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java49
-rw-r--r--services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java8
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java182
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java34
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java17
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java5
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java26
-rw-r--r--services/core/java/com/android/server/power/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java23
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java34
-rw-r--r--services/core/java/com/android/server/wm/ActivitySnapshotController.java16
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java4
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java16
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOverrides.java18
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java4
-rw-r--r--services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java20
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java4
-rw-r--r--services/core/java/com/android/server/wm/DragState.java39
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java7
-rw-r--r--services/core/java/com/android/server/wm/Task.java18
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java24
-rw-r--r--services/core/java/com/android/server/wm/TaskPersister.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java13
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotPersister.java15
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java83
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java54
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java1
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java2
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java56
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java17
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java41
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig10
-rw-r--r--services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java76
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java48
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java24
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java36
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java50
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java36
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java3
304 files changed, 6251 insertions, 2700 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a60ced5835ea..f249884cb1a0 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -107,7 +107,7 @@ aconfig_declarations_group {
"com.android.server.flags.services-aconfig-java",
"com.android.text.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
- "configinfra_framework_flags_java_lib",
+ "configinfra_framework_flags_java_exported_lib",
"conscrypt_exported_aconfig_flags_lib",
"device_policy_aconfig_flags_lib",
"display_flags_lib",
@@ -467,7 +467,7 @@ java_aconfig_library {
"//apex_available:platform",
"com.android.art",
"com.android.art.debug",
- "com.android.btservices",
+ "com.android.bt",
"com.android.mediaprovider",
"com.android.permission",
],
diff --git a/api/api.go b/api/api.go
index cbdb7e81ab86..640773be0f9b 100644
--- a/api/api.go
+++ b/api/api.go
@@ -104,8 +104,9 @@ func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) {
}
func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- ctx.WalkDeps(func(child, parent android.Module) bool {
- if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" {
+ ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+ javaInfo, ok := android.OtherModuleProvider(ctx, child, java.JavaInfoProvider)
+ if ok && javaInfo.AndroidLibraryDependencyInfo != nil && child.Name() != "framework-res" {
// Stubs of BCP and SSCP libraries should not have any dependencies on apps
// This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true
ctx.ModuleErrorf(
diff --git a/core/api/current.txt b/core/api/current.txt
index b7f7a7f9e779..19f68eb0c787 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -38278,7 +38278,6 @@ package android.provider {
field public static final String ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS = "android.settings.NOTIFICATION_LISTENER_DETAIL_SETTINGS";
field public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS";
- field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_NUMBERING_SYSTEM_SETTINGS = "android.settings.NUMBERING_SYSTEM_SETTINGS";
field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS";
field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = "android.settings.PROCESS_WIFI_EASY_CONNECT_URI";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a352d9d2ea06..b118c7b6fed8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -436,34 +436,6 @@ package android.app {
ctor public PictureInPictureUiState(boolean);
}
- public class PropertyInvalidatedCache<Query, Result> {
- ctor public PropertyInvalidatedCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
- method @NonNull public static String createPropertyName(@NonNull String, @NonNull String);
- method public void disableForCurrentProcess();
- method public static void disableForCurrentProcess(@NonNull String);
- method public static void disableForTestMode();
- method public final void disableInstance();
- method public final void disableSystemWide();
- method public final void forgetDisableLocal();
- method public boolean getDisabledState();
- method public void invalidateCache();
- method public static void invalidateCache(@NonNull String, @NonNull String);
- method public final boolean isDisabled();
- method @Nullable public Result query(@NonNull Query);
- method public static void setTestMode(boolean);
- method public void testPropertyName();
- field public static final String MODULE_BLUETOOTH = "bluetooth";
- field public static final String MODULE_SYSTEM = "system_server";
- field public static final String MODULE_TELEPHONY = "telephony";
- field public static final String MODULE_TEST = "test";
- }
-
- public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
- ctor public PropertyInvalidatedCache.QueryHandler();
- method @Nullable public abstract R apply(@NonNull Q);
- method public boolean shouldBypassCache(@NonNull Q);
- }
-
public class StatusBarManager {
method public void cancelRequestAddTile(@NonNull String);
method public void clickNotification(@Nullable String, int, int, boolean);
@@ -2444,17 +2416,28 @@ package android.os {
method @FlaggedApi("android.os.mainline_vcn_platform_api") public final void removeEqualMessages(int, @Nullable Object);
}
- public class IpcDataCache<Query, Result> extends android.app.PropertyInvalidatedCache<Query,Result> {
+ public class IpcDataCache<Query, Result> {
ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>);
+ method public void disableForCurrentProcess();
method public static void disableForCurrentProcess(@NonNull String);
+ method public final void disableInstance();
+ method public final void disableSystemWide();
+ method public final void forgetDisableLocal();
+ method public boolean getDisabledState();
+ method public void invalidateCache();
method public static void invalidateCache(@NonNull String, @NonNull String);
+ method public final boolean isDisabled();
+ method @Nullable public Result query(@NonNull Query);
+ method public static void setTestMode(boolean);
field public static final String MODULE_BLUETOOTH = "bluetooth";
field public static final String MODULE_SYSTEM = "system_server";
field public static final String MODULE_TEST = "test";
}
- public abstract static class IpcDataCache.QueryHandler<Q, R> extends android.app.PropertyInvalidatedCache.QueryHandler<Q,R> {
+ public abstract static class IpcDataCache.QueryHandler<Q, R> {
ctor public IpcDataCache.QueryHandler();
+ method @Nullable public abstract R apply(@NonNull Q);
+ method public boolean shouldBypassCache(@NonNull Q);
}
public final class MessageQueue {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 0451ac0d6897..1738a92b7672 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -114,6 +114,7 @@ interface INotificationManager
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, String conversationId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, String channelId);
ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
+ ParceledListSlice getOrCreateNotificationChannels(String callingPkg, String targetPkg, int userId, boolean createPrefsIfNeeded);
ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
int getDeletedChannelCount(String pkg, int uid);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index aede8aa70ede..24f2495d8f09 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -68,6 +68,7 @@ import android.service.notification.StatusBarNotification;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
+import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
@@ -77,6 +78,8 @@ import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
+import java.time.Instant;
import java.time.InstantSource;
import java.util.ArrayList;
import java.util.Arrays;
@@ -661,8 +664,10 @@ public class NotificationManager {
mCallNotificationEventCallbacks = new HashMap<>();
private final InstantSource mClock;
- private final RateEstimator mUpdateRateEstimator = new RateEstimator();
- private final RateEstimator mUnnecessaryCancelRateEstimator = new RateEstimator();
+ private final RateLimiter mUpdateRateLimiter = new RateLimiter("notify (update)",
+ MAX_NOTIFICATION_UPDATE_RATE);
+ private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)",
+ MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE);
// Value is KNOWN_STATUS_ENQUEUED/_CANCELLED
private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100);
private final Object mThrottleLock = new Object();
@@ -820,21 +825,16 @@ public class NotificationManager {
if (Flags.nmBinderPerfThrottleNotify()) {
NotificationKey key = new NotificationKey(user, pkg, tag, id);
- long now = mClock.millis();
synchronized (mThrottleLock) {
Integer status = mKnownNotifications.get(key);
if (status != null && status == KNOWN_STATUS_ENQUEUED
&& !notification.hasCompletedProgress()) {
- float updateRate = mUpdateRateEstimator.getRate(now);
- if (updateRate > MAX_NOTIFICATION_UPDATE_RATE) {
- Slog.w(TAG, "Shedding update of " + key
- + ", notification update maximum rate exceeded (" + updateRate
- + ")");
+ if (mUpdateRateLimiter.eventExceedsRate()) {
+ mUpdateRateLimiter.recordRejected(key);
return true;
}
- mUpdateRateEstimator.update(now);
+ mUpdateRateLimiter.recordAccepted();
}
-
mKnownNotifications.put(key, KNOWN_STATUS_ENQUEUED);
}
}
@@ -845,6 +845,51 @@ public class NotificationManager {
private record NotificationKey(@NonNull UserHandle user, @NonNull String pkg,
@Nullable String tag, int id) { }
+ /** Helper class to rate-limit Binder calls. */
+ private class RateLimiter {
+
+ private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(5);
+
+ private final RateEstimator mInputRateEstimator;
+ private final RateEstimator mOutputRateEstimator;
+ private final String mName;
+ private final float mLimitRate;
+
+ private Instant mLogSilencedUntil;
+
+ private RateLimiter(String name, float limitRate) {
+ mInputRateEstimator = new RateEstimator();
+ mOutputRateEstimator = new RateEstimator();
+ mName = name;
+ mLimitRate = limitRate;
+ }
+
+ boolean eventExceedsRate() {
+ long nowMillis = mClock.millis();
+ mInputRateEstimator.update(nowMillis);
+ return mOutputRateEstimator.getRate(nowMillis) > mLimitRate;
+ }
+
+ void recordAccepted() {
+ mOutputRateEstimator.update(mClock.millis());
+ }
+
+ void recordRejected(NotificationKey key) {
+ Instant now = mClock.instant();
+ if (mLogSilencedUntil != null && now.isBefore(mLogSilencedUntil)) {
+ return;
+ }
+
+ long nowMillis = now.toEpochMilli();
+ Slog.w(TAG, TextUtils.formatSimple(
+ "Shedding %s of %s, rate limit (%s) exceeded: input %s, output would be %s",
+ mName, key, mLimitRate, mInputRateEstimator.getRate(nowMillis),
+ mOutputRateEstimator.getRate(nowMillis)));
+
+ mLogSilencedUntil = now.plus(RATE_LIMITER_LOG_INTERVAL);
+ }
+ }
+
private Notification fixNotification(Notification notification) {
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
@@ -967,18 +1012,14 @@ public class NotificationManager {
private boolean discardCancel(UserHandle user, String pkg, @Nullable String tag, int id) {
if (Flags.nmBinderPerfThrottleNotify()) {
NotificationKey key = new NotificationKey(user, pkg, tag, id);
- long now = mClock.millis();
synchronized (mThrottleLock) {
Integer status = mKnownNotifications.get(key);
if (status != null && status == KNOWN_STATUS_CANCELLED) {
- float cancelRate = mUnnecessaryCancelRateEstimator.getRate(now);
- if (cancelRate > MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE) {
- Slog.w(TAG, "Shedding cancel of " + key
- + ", presumably unnecessary and maximum rate exceeded ("
- + cancelRate + ")");
+ if (mUnnecessaryCancelRateLimiter.eventExceedsRate()) {
+ mUnnecessaryCancelRateLimiter.recordRejected(key);
return true;
}
- mUnnecessaryCancelRateEstimator.update(now);
+ mUnnecessaryCancelRateLimiter.recordAccepted();
}
mKnownNotifications.put(key, KNOWN_STATUS_CANCELLED);
}
@@ -1211,7 +1252,8 @@ public class NotificationManager {
mNotificationChannelListCache.query(new NotificationChannelQuery(
mContext.getOpPackageName(),
mContext.getPackageName(),
- mContext.getUserId())));
+ mContext.getUserId(),
+ true))); // create (default channel) if needed
} else {
INotificationManager service = service();
try {
@@ -1239,7 +1281,8 @@ public class NotificationManager {
mNotificationChannelListCache.query(new NotificationChannelQuery(
mContext.getOpPackageName(),
mContext.getPackageName(),
- mContext.getUserId())));
+ mContext.getUserId(),
+ true))); // create (default channel) if needed
} else {
INotificationManager service = service();
try {
@@ -1263,9 +1306,10 @@ public class NotificationManager {
public List<NotificationChannel> getNotificationChannels() {
if (Flags.nmBinderPerfCacheChannels()) {
return mNotificationChannelListCache.query(new NotificationChannelQuery(
- mContext.getOpPackageName(),
- mContext.getPackageName(),
- mContext.getUserId()));
+ mContext.getOpPackageName(),
+ mContext.getPackageName(),
+ mContext.getUserId(),
+ false));
} else {
INotificationManager service = service();
try {
@@ -1405,8 +1449,8 @@ public class NotificationManager {
public List<NotificationChannel> apply(NotificationChannelQuery query) {
INotificationManager service = service();
try {
- return service.getNotificationChannels(query.callingPkg,
- query.targetPkg, query.userId).getList();
+ return service.getOrCreateNotificationChannels(query.callingPkg,
+ query.targetPkg, query.userId, query.createIfNeeded).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1434,7 +1478,8 @@ public class NotificationManager {
private record NotificationChannelQuery(
String callingPkg,
String targetPkg,
- int userId) {}
+ int userId,
+ boolean createIfNeeded) {}
/**
* @hide
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index e7e9b0027812..660d88007c9a 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -22,7 +22,6 @@ import static com.android.internal.util.Preconditions.checkArgumentPositive;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
@@ -77,7 +76,6 @@ import java.util.concurrent.atomic.AtomicLong;
* @param <Result> The class holding cache entries; use a boxed primitive if possible
* @hide
*/
-@TestApi
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class PropertyInvalidatedCache<Query, Result> {
/**
@@ -95,7 +93,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* This is a configuration class that customizes a cache instance.
* @hide
*/
- @TestApi
public static abstract class QueryHandler<Q,R> {
/**
* Compute a result given a query. The semantics are those of Functor.
@@ -134,7 +131,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* the system has permissions to write properties with this module.
* @hide
*/
- @TestApi
public static final String MODULE_TEST = "test";
/**
@@ -142,18 +138,17 @@ public class PropertyInvalidatedCache<Query, Result> {
* the system processes.
* @hide
*/
- @TestApi
public static final String MODULE_SYSTEM = "system_server";
/**
* The module used for bluetooth caches.
* @hide
*/
- @TestApi
public static final String MODULE_BLUETOOTH = "bluetooth";
/**
* The module used for telephony caches.
+ * @hide
*/
public static final String MODULE_TELEPHONY = "telephony";
@@ -171,7 +166,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* error message.
* @hide
*/
- @TestApi
public static @NonNull String createPropertyName(@NonNull String module,
@NonNull String apiName) {
char[] api = apiName.toCharArray();
@@ -1382,7 +1376,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* @param computer The code to compute values that are not in the cache.
* @hide
*/
- @TestApi
public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
@NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
@@ -1409,7 +1402,7 @@ public class PropertyInvalidatedCache<Query, Result> {
* current logic does not care.
* @hide
*/
- @TestApi
+ @VisibleForTesting
public static void setTestMode(boolean mode) {
synchronized (sGlobalLock) {
if (sTestMode == mode) {
@@ -1450,7 +1443,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* must be true when this method is called.
* @hide
*/
- @TestApi
public void testPropertyName() {
synchronized (sGlobalLock) {
if (sTestMode == false) {
@@ -1542,8 +1534,8 @@ public class PropertyInvalidatedCache<Query, Result> {
* be re-enabled.
* @hide
*/
- @TestApi
- public final void disableInstance() {
+ @VisibleForTesting
+ public void disableInstance() {
synchronized (mLock) {
mDisabled = true;
clear();
@@ -1579,8 +1571,8 @@ public class PropertyInvalidatedCache<Query, Result> {
* found in the list of disabled caches.
* @hide
*/
- @TestApi
- public final void forgetDisableLocal() {
+ @VisibleForTesting
+ public void forgetDisableLocal() {
synchronized (sGlobalLock) {
sDisabledKeys.remove(mCacheName);
}
@@ -1603,13 +1595,11 @@ public class PropertyInvalidatedCache<Query, Result> {
* property. Once disabled, a cache cannot be reenabled.
* @hide
*/
- @TestApi
public void disableForCurrentProcess() {
disableLocal(mCacheName);
}
/** @hide */
- @TestApi
public static void disableForCurrentProcess(@NonNull String cacheName) {
disableLocal(cacheName);
}
@@ -1618,8 +1608,8 @@ public class PropertyInvalidatedCache<Query, Result> {
* Return whether a cache instance is disabled.
* @hide
*/
- @TestApi
- public final boolean isDisabled() {
+ @VisibleForTesting
+ public boolean isDisabled() {
return mDisabled || !sEnabled;
}
@@ -1627,7 +1617,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* Get a value from the cache or recompute it.
* @hide
*/
- @TestApi
public @Nullable Result query(@NonNull Query query) {
// Let access to mDisabled race: it's atomic anyway.
long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
@@ -1767,8 +1756,8 @@ public class PropertyInvalidatedCache<Query, Result> {
* just use the static version of this function.
* @hide
*/
- @TestApi
- public final void disableSystemWide() {
+ @VisibleForTesting
+ public void disableSystemWide() {
disableSystemWide(mPropertyName);
}
@@ -1788,7 +1777,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* to look up the NonceHandler for a given property name.
* @hide
*/
- @TestApi
public void invalidateCache() {
mNonce.invalidate();
}
@@ -1817,7 +1805,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* Invalidate caches in all processes that are keyed for the module and api.
* @hide
*/
- @TestApi
public static void invalidateCache(@NonNull String module, @NonNull String api) {
invalidateCache(createPropertyName(module, api));
}
@@ -2059,7 +2046,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* temporarily disable caching, use the corking mechanism.
* @hide
*/
- @TestApi
public static void disableForTestMode() {
Log.d(TAG, "disabling all caches in the process");
sEnabled = false;
@@ -2068,10 +2054,8 @@ public class PropertyInvalidatedCache<Query, Result> {
/**
* Report the disabled status of this cache instance. The return value does not
* reflect status of the property key.
- * @hide
*/
- @TestApi
- public boolean getDisabledState() {
+ private boolean getDisabledState() {
return isDisabled();
}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index b1db1379e400..edd17e8bb552 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -13,6 +13,13 @@ flag {
}
flag {
+ name: "notifications_redesign_themed_app_icons"
+ namespace: "systemui"
+ description: "Notifications Redesign: Experiment to make app icons in notifications themed"
+ bug: "371174789"
+}
+
+flag {
name: "notifications_redesign_templates"
namespace: "systemui"
description: "Notifications Redesign: Update notification templates"
@@ -174,16 +181,6 @@ flag {
}
flag {
- name: "update_ranking_time"
- namespace: "systemui"
- description: "Updates notification sorting criteria to highlight new content while maintaining stability"
- bug: "326016985"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "sort_section_by_time"
namespace: "systemui"
description: "Changes notification sort order to be by time within a section"
diff --git a/core/java/android/app/supervision/ISupervisionAppService.aidl b/core/java/android/app/supervision/ISupervisionAppService.aidl
new file mode 100644
index 000000000000..033998fc4a5b
--- /dev/null
+++ b/core/java/android/app/supervision/ISupervisionAppService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.supervision;
+
+/**
+ * @hide
+ */
+interface ISupervisionAppService {
+}
diff --git a/core/java/android/app/supervision/SupervisionAppService.java b/core/java/android/app/supervision/SupervisionAppService.java
new file mode 100644
index 000000000000..4468c78cbd34
--- /dev/null
+++ b/core/java/android/app/supervision/SupervisionAppService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.supervision;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Base class for a service that the {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION}
+ * role holder must implement.
+ *
+ * @hide
+ */
+public class SupervisionAppService extends Service {
+ private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() {
+ };
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
+}
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 1b0353274fb9..4ee3a0360b43 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -32,3 +32,11 @@ flag {
description: "Flag that deprecates supervision methods in DPM"
bug: "382034839"
}
+
+flag {
+ name: "enable_supervision_app_service"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag to enable the SupervisionAppService"
+ bug: "389123070"
+}
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 78c8954cfe5f..88d69b665c87 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -25,10 +25,15 @@ import android.database.DatabaseErrorHandler;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.os.FileUtils;
+import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.File;
+import java.io.IOException;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
/**
* A helper class to manage database creation and version management.
@@ -54,6 +59,13 @@ import java.util.Objects;
public abstract class SQLiteOpenHelper implements AutoCloseable {
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
+ // Every database file has a lock, saved in this map. The lock is held while the database is
+ // opened.
+ private static final ConcurrentHashMap<String, Object> sDbLock = new ConcurrentHashMap<>();
+
+ // The lock that this open helper instance must hold when the database is opened.
+ private final Object mLock;
+
private final Context mContext;
@UnsupportedAppUsage
private final String mName;
@@ -168,6 +180,21 @@ public abstract class SQLiteOpenHelper implements AutoCloseable {
mNewVersion = version;
mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
setOpenParamsBuilder(openParamsBuilder);
+
+ Object lock = null;
+ if (mName == null || !Flags.concurrentOpenHelper()) {
+ lock = new Object();
+ } else {
+ try {
+ final String path = mContext.getDatabasePath(mName).getCanonicalPath();
+ lock = sDbLock.computeIfAbsent(path, (String k) -> new Object());
+ } catch (IOException e) {
+ Log.d(TAG, "failed to construct db path for " + mName);
+ // Ensure the lock is not null.
+ lock = new Object();
+ }
+ }
+ mLock = lock;
}
/**
@@ -358,74 +385,77 @@ public abstract class SQLiteOpenHelper implements AutoCloseable {
SQLiteDatabase db = mDatabase;
try {
- mIsInitializing = true;
+ synchronized (mLock) {
+ mIsInitializing = true;
- if (db != null) {
- if (writable && db.isReadOnly()) {
- db.reopenReadWrite();
- }
- } else if (mName == null) {
- db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
- } else {
- final File filePath = mContext.getDatabasePath(mName);
- SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
- try {
- db = SQLiteDatabase.openDatabase(filePath, params);
- // Keep pre-O-MR1 behavior by resetting file permissions to 660
- setFilePermissionsForDb(filePath.getPath());
- } catch (SQLException ex) {
- if (writable) {
- throw ex;
+ if (db != null) {
+ if (writable && db.isReadOnly()) {
+ db.reopenReadWrite();
+ }
+ } else if (mName == null) {
+ db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
+ } else {
+ final File filePath = mContext.getDatabasePath(mName);
+ SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
+ try {
+ db = SQLiteDatabase.openDatabase(filePath, params);
+ // Keep pre-O-MR1 behavior by resetting file permissions to 660
+ setFilePermissionsForDb(filePath.getPath());
+ } catch (SQLException ex) {
+ if (writable) {
+ throw ex;
+ }
+ Log.e(TAG, "Couldn't open database for writing (will try read-only):", ex);
+ params = params.toBuilder()
+ .addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
+ db = SQLiteDatabase.openDatabase(filePath, params);
}
- Log.e(TAG, "Couldn't open database for writing (will try read-only):", ex);
- params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
- db = SQLiteDatabase.openDatabase(filePath, params);
}
- }
-
- onConfigure(db);
- final int version = db.getVersion();
- if (version != mNewVersion) {
- if (db.isReadOnly()) {
- throw new SQLiteException("Can't upgrade read-only database from version " +
- db.getVersion() + " to " + mNewVersion + ": " + mName);
- }
+ onConfigure(db);
- if (version > 0 && version < mMinimumSupportedVersion) {
- File databaseFile = new File(db.getPath());
- onBeforeDelete(db);
- db.close();
- if (SQLiteDatabase.deleteDatabase(databaseFile)) {
- mIsInitializing = false;
- return getDatabaseLocked(writable);
- } else {
- throw new IllegalStateException("Unable to delete obsolete database "
- + mName + " with version " + version);
+ final int version = db.getVersion();
+ if (version != mNewVersion) {
+ if (db.isReadOnly()) {
+ throw new SQLiteException("Can't upgrade read-only database from version "
+ + db.getVersion() + " to " + mNewVersion + ": " + mName);
}
- } else {
- db.beginTransaction();
- try {
- if (version == 0) {
- onCreate(db);
+
+ if (version > 0 && version < mMinimumSupportedVersion) {
+ File databaseFile = new File(db.getPath());
+ onBeforeDelete(db);
+ db.close();
+ if (SQLiteDatabase.deleteDatabase(databaseFile)) {
+ mIsInitializing = false;
+ return getDatabaseLocked(writable);
} else {
- if (version > mNewVersion) {
- onDowngrade(db, version, mNewVersion);
+ throw new IllegalStateException("Unable to delete obsolete database "
+ + mName + " with version " + version);
+ }
+ } else {
+ db.beginTransaction();
+ try {
+ if (version == 0) {
+ onCreate(db);
} else {
- onUpgrade(db, version, mNewVersion);
+ if (version > mNewVersion) {
+ onDowngrade(db, version, mNewVersion);
+ } else {
+ onUpgrade(db, version, mNewVersion);
+ }
}
+ db.setVersion(mNewVersion);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
}
- db.setVersion(mNewVersion);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
}
}
- }
- onOpen(db);
- mDatabase = db;
- return db;
+ onOpen(db);
+ mDatabase = db;
+ return db;
+ }
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index d43a66904af8..1d17a51f3653 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -17,3 +17,12 @@ flag {
description: "SQLite APIs held back for Android 15"
bug: "279043253"
}
+
+flag {
+ name: "concurrent_open_helper"
+ is_exported: true
+ namespace: "system_performance"
+ is_fixed_read_only: false
+ description: "Make SQLiteOpenHelper thread-safe"
+ bug: "335904370"
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 25cdc508fdce..14911de6d032 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -107,6 +107,13 @@ public class HubEndpoint {
@GuardedBy("mLock")
private final SparseArray<HubEndpointSession> mActiveSessions = new SparseArray<>();
+ /*
+ * Internal interface used to invoke IContextHubEndpoint calls.
+ */
+ interface EndpointConsumer {
+ void accept(IContextHubEndpoint endpoint) throws RemoteException;
+ }
+
private final IContextHubEndpointCallback mServiceCallback =
new IContextHubEndpointCallback.Stub() {
@Override
@@ -115,20 +122,19 @@ public class HubEndpoint {
HubEndpointInfo initiator,
@Nullable String serviceDescriptor)
throws RemoteException {
- HubEndpointSession activeSession;
+ boolean sessionExists;
synchronized (mLock) {
- activeSession = mActiveSessions.get(sessionId);
+ sessionExists = mActiveSessions.contains(sessionId);
// TODO(b/378974199): Consider refactor these assertions
- if (activeSession != null) {
- Log.i(
+ if (sessionExists) {
+ Log.w(
TAG,
"onSessionOpenComplete: session already exists, id="
+ sessionId);
- return;
}
}
- if (mLifecycleCallback != null) {
+ if (!sessionExists && mLifecycleCallback != null) {
mLifecycleCallbackExecutor.execute(
() ->
processSessionOpenRequestResult(
@@ -142,96 +148,6 @@ public class HubEndpoint {
}
}
- private void processSessionOpenRequestResult(
- int sessionId,
- HubEndpointInfo initiator,
- @Nullable String serviceDescriptor,
- HubEndpointSessionResult result) {
- if (result == null) {
- throw new IllegalArgumentException(
- "HubEndpointSessionResult shouldn't be null.");
- }
-
- if (result.isAccepted()) {
- acceptSession(sessionId, initiator, serviceDescriptor);
- } else {
- Log.i(
- TAG,
- "Session "
- + sessionId
- + " from "
- + initiator
- + " was rejected, reason="
- + result.getReason());
- rejectSession(sessionId);
- }
-
- invokeCallbackFinished();
- }
-
- private void acceptSession(
- int sessionId,
- HubEndpointInfo initiator,
- @Nullable String serviceDescriptor) {
- if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
- // No longer registered?
- return;
- }
-
- // Retrieve the active session
- HubEndpointSession activeSession;
- synchronized (mLock) {
- activeSession = mActiveSessions.get(sessionId);
- // TODO(b/378974199): Consider refactor these assertions
- if (activeSession != null) {
- Log.e(
- TAG,
- "onSessionOpenRequestResult: session already exists, id="
- + sessionId);
- return;
- }
-
- activeSession =
- new HubEndpointSession(
- sessionId,
- HubEndpoint.this,
- mAssignedHubEndpointInfo,
- initiator,
- serviceDescriptor);
- try {
- // oneway call to notify system service that the request is completed
- mServiceToken.openSessionRequestComplete(sessionId);
- } catch (RemoteException e) {
- Log.e(TAG, "onSessionOpenRequestResult: ", e);
- return;
- }
-
- mActiveSessions.put(sessionId, activeSession);
- }
-
- // Execute the callback
- activeSession.setOpened();
- if (mLifecycleCallback != null) {
- final HubEndpointSession finalActiveSession = activeSession;
- mLifecycleCallbackExecutor.execute(
- () -> mLifecycleCallback.onSessionOpened(finalActiveSession));
- }
- }
-
- private void rejectSession(int sessionId) {
- if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
- // No longer registered?
- return;
- }
-
- try {
- mServiceToken.closeSession(
- sessionId, REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
-
@Override
public void onSessionOpenComplete(int sessionId) throws RemoteException {
final HubEndpointSession activeSession;
@@ -242,16 +158,15 @@ public class HubEndpoint {
}
// TODO(b/378974199): Consider refactor these assertions
if (activeSession == null) {
- Log.i(
+ Log.w(
TAG,
"onSessionOpenComplete: no pending session open request? id="
+ sessionId);
- return;
+ } else {
+ activeSession.setOpened();
}
- // Execute the callback
- activeSession.setOpened();
- if (mLifecycleCallback != null) {
+ if (activeSession != null && mLifecycleCallback != null) {
mLifecycleCallbackExecutor.execute(
() -> {
mLifecycleCallback.onSessionOpened(activeSession);
@@ -272,12 +187,11 @@ public class HubEndpoint {
}
// TODO(b/378974199): Consider refactor these assertions
if (activeSession == null) {
- Log.i(TAG, "onSessionClosed: session not active, id=" + sessionId);
- return;
+ Log.w(TAG, "onSessionClosed: session not active, id=" + sessionId);
}
// Execute the callback
- if (mLifecycleCallback != null) {
+ if (activeSession != null && mLifecycleCallback != null) {
mLifecycleCallbackExecutor.execute(
() -> {
mLifecycleCallback.onSessionClosed(activeSession, reason);
@@ -304,44 +218,120 @@ public class HubEndpoint {
activeSession = mActiveSessions.get(sessionId);
}
if (activeSession == null) {
- Log.i(TAG, "onMessageReceived: session not active, id=" + sessionId);
+ Log.w(TAG, "onMessageReceived: session not active, id=" + sessionId);
}
if (activeSession == null || mMessageCallback == null) {
- if (message.isResponseRequired()) {
- try {
- mServiceToken.sendMessageDeliveryStatus(
- sessionId,
- message.getMessageSequenceNumber(),
- ErrorCode.DESTINATION_NOT_FOUND);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
- return;
+ sendMessageDeliveryStatus(
+ sessionId, message, ErrorCode.DESTINATION_NOT_FOUND);
+ } else {
+ mMessageCallbackExecutor.execute(
+ () -> {
+ mMessageCallback.onMessageReceived(activeSession, message);
+ sendMessageDeliveryStatus(sessionId, message, ErrorCode.OK);
+ });
}
+ }
- // Execute the callback
- mMessageCallbackExecutor.execute(
- () -> {
- mMessageCallback.onMessageReceived(activeSession, message);
- if (message.isResponseRequired()) {
- try {
- mServiceToken.sendMessageDeliveryStatus(
+ private void sendMessageDeliveryStatus(
+ int sessionId, HubMessage message, byte errorCode) {
+ if (message.isResponseRequired()) {
+ invokeCallback(
+ (callback) ->
+ callback.sendMessageDeliveryStatus(
sessionId,
message.getMessageSequenceNumber(),
- ErrorCode.OK);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
- invokeCallbackFinished();
- });
+ errorCode));
+ }
+ invokeCallbackFinished();
+ }
+
+ private void processSessionOpenRequestResult(
+ int sessionId,
+ HubEndpointInfo initiator,
+ @Nullable String serviceDescriptor,
+ HubEndpointSessionResult result) {
+ if (result == null) {
+ throw new IllegalArgumentException(
+ "HubEndpointSessionResult shouldn't be null.");
+ }
+
+ if (result.isAccepted()) {
+ acceptSession(sessionId, initiator, serviceDescriptor);
+ } else {
+ Log.e(
+ TAG,
+ "Session "
+ + sessionId
+ + " from "
+ + initiator
+ + " was rejected, reason="
+ + result.getReason());
+ rejectSession(sessionId);
+ }
+
+ invokeCallbackFinished();
+ }
+
+ private void acceptSession(
+ int sessionId,
+ HubEndpointInfo initiator,
+ @Nullable String serviceDescriptor) {
+ // Retrieve the active session
+ HubEndpointSession activeSession;
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession != null) {
+ Log.e(
+ TAG,
+ "onSessionOpenRequestResult: session already exists, id="
+ + sessionId);
+ return;
+ }
+
+ activeSession =
+ new HubEndpointSession(
+ sessionId,
+ HubEndpoint.this,
+ mAssignedHubEndpointInfo,
+ initiator,
+ serviceDescriptor);
+
+ invokeCallback(
+ (callback) -> callback.openSessionRequestComplete(sessionId));
+ mActiveSessions.put(sessionId, activeSession);
+ }
+
+ // Execute the callback
+ activeSession.setOpened();
+ if (mLifecycleCallback != null) {
+ final HubEndpointSession finalActiveSession = activeSession;
+ mLifecycleCallbackExecutor.execute(
+ () -> mLifecycleCallback.onSessionOpened(finalActiveSession));
+ }
+ }
+
+ private void rejectSession(int sessionId) {
+ invokeCallback(
+ (callback) ->
+ callback.closeSession(
+ sessionId,
+ REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED));
}
private void invokeCallbackFinished() {
+ invokeCallback((callback) -> callback.onCallbackFinished());
+ }
+
+ private void invokeCallback(EndpointConsumer consumer) {
try {
- mServiceToken.onCallbackFinished();
+ consumer.accept(mServiceToken);
+ } catch (IllegalStateException e) {
+ // It's possible to hit this exception if the endpoint was unregistered
+ // while processing the callback. It's not a fatal error so we just log
+ // a warning.
+ Log.w(TAG, "IllegalStateException while calling callback", e);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -369,11 +359,6 @@ public class HubEndpoint {
/** @hide */
public void register(IContextHubService service) {
- // TODO(b/378974199): Consider refactor these assertions
- if (mServiceToken != null) {
- // Already registered
- return;
- }
try {
IContextHubEndpoint serviceToken =
service.registerEndpoint(
@@ -391,13 +376,6 @@ public class HubEndpoint {
/** @hide */
public void unregister() {
- IContextHubEndpoint serviceToken = mServiceToken;
- // TODO(b/378974199): Consider refactor these assertions
- if (serviceToken == null) {
- // Not yet registered
- return;
- }
-
try {
synchronized (mLock) {
// Don't call HubEndpointSession.close() here.
@@ -410,20 +388,11 @@ public class HubEndpoint {
} catch (RemoteException e) {
Log.e(TAG, "unregisterEndpoint: failed to unregister endpoint", e);
e.rethrowFromSystemServer();
- } finally {
- mServiceToken = null;
- mAssignedHubEndpointInfo = null;
}
}
/** @hide */
public void openSession(HubEndpointInfo destinationInfo, @Nullable String serviceDescriptor) {
- // TODO(b/378974199): Consider refactor these assertions
- if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
- // No longer registered?
- return;
- }
-
HubEndpointSession newSession;
try {
synchronized (mLock) {
@@ -449,13 +418,6 @@ public class HubEndpoint {
/** @hide */
public void closeSession(HubEndpointSession session) {
- IContextHubEndpoint serviceToken = mServiceToken;
- // TODO(b/378974199): Consider refactor these assertions
- if (serviceToken == null || mAssignedHubEndpointInfo == null) {
- // Not registered
- return;
- }
-
synchronized (mLock) {
if (!mActiveSessions.contains(session.getId())) {
// Already closed?
@@ -466,8 +428,7 @@ public class HubEndpoint {
}
try {
- // Oneway notification to system service
- serviceToken.closeSession(session.getId(), REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
+ mServiceToken.closeSession(session.getId(), REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
} catch (RemoteException e) {
Log.e(TAG, "closeSession: failed to close session " + session, e);
e.rethrowFromSystemServer();
@@ -478,14 +439,8 @@ public class HubEndpoint {
HubEndpointSession session,
HubMessage message,
@Nullable IContextHubTransactionCallback transactionCallback) {
- IContextHubEndpoint serviceToken = mServiceToken;
- if (serviceToken == null) {
- // Not registered
- return;
- }
-
try {
- serviceToken.sendMessage(session.getId(), message, transactionCallback);
+ mServiceToken.sendMessage(session.getId(), message, transactionCallback);
} catch (RemoteException e) {
Log.e(TAG, "sendMessage: failed to send message session=" + session, e);
e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 7887c15a72ff..62126963cba4 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -178,6 +178,13 @@ flag {
}
flag {
+ name: "enable_display_color_inversion_key_gestures"
+ namespace: "input"
+ description: "Adds key gestures for display color inversion for accessibility needs"
+ bug: "383730505"
+}
+
+flag {
name: "enable_talkback_and_magnifier_key_gestures"
namespace: "input"
description: "Adds key gestures for talkback and magnifier"
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index a2e9314f6436..240bc4f74820 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -658,4 +658,74 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
}
});
}
+
+ /**
+ * The following APIs are exposed to support testing. They only forward the superclass but
+ * that means the superclass does not have to expose the APIs itself.
+ */
+
+ /**
+ * Stop disabling local caches with the same name as <this>. Any caches that are currently
+ * disabled remain disabled (the "disabled" setting is sticky). However, new caches with this
+ * name will not be disabled. It is not an error if the cache name is not found in the list
+ * of disabled caches.
+ * @hide
+ */
+ @TestApi
+ @Override
+ public final void forgetDisableLocal() {
+ super.forgetDisableLocal();
+ }
+
+ /**
+ * Return whether a cache instance is disabled.
+ * @hide
+ */
+ @TestApi
+ @Override
+ public final boolean isDisabled() {
+ return super.isDisabled();
+ }
+
+ /**
+ * This is an obsolete synonym for {@link #isDisabled()}.
+ * @hide
+ */
+ @TestApi
+ public boolean getDisabledState() {
+ return isDisabled();
+ }
+
+ /**
+ * Disable the use of this cache in this process. This method is used internally and during
+ * testing. To disable a cache in normal code, use disableProcessLocal().
+ * @hide
+ */
+ @TestApi
+ @Override
+ public final void disableInstance() {
+ super.disableInstance();
+ }
+
+ /**
+ * Disable all caches that use the property as the current cache.
+ * @hide
+ */
+ @TestApi
+ @Override
+ public final void disableSystemWide() {
+ super.disableSystemWide();
+ }
+
+ /**
+ * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is
+ * illegal to clear the test mode if the test mode is already off. The purpose is solely to
+ * ensure that test clients do not forget to use the test mode properly, even though the
+ * current logic does not care.
+ * @hide
+ */
+ @TestApi
+ public static void setTestMode(boolean mode) {
+ PropertyInvalidatedCache.setTestMode(mode);
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index baaaa464a4cf..0cfec2cc7314 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1254,21 +1254,6 @@ public final class Settings {
"android.settings.TEMPERATURE_UNIT_SETTINGS";
/**
- * Activity Action: Show numbering system configuration settings.
- * <p>
- * Input: Nothing.
- * <p>
- * Output: After calling {@link android.app.Activity#startActivityForResult}, the callback
- * {@code onActivityResult} will have resultCode {@link android.app.Activity#RESULT_OK} if
- * the numbering system settings page is suitable to show on the UI. Otherwise, the result is
- * set to {@link android.app.Activity#RESULT_CANCELED}.
- */
- @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_NUMBERING_SYSTEM_SETTINGS =
- "android.settings.NUMBERING_SYSTEM_SETTINGS";
-
- /**
* Activity Action: Show measurement system configuration settings.
* <p>
* Input: Nothing.
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 146c2b6fa46e..105fa3ffd4cd 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -30,13 +30,17 @@ import android.metrics.LogMaker;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Trace;
import android.os.UserHandle;
+import android.util.ArrayMap;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif;
import java.util.ArrayList;
+import java.util.Map;
/**
* Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
@@ -69,7 +73,15 @@ public class StatusBarNotification implements Parcelable {
// A small per-notification ID, used for statsd logging.
private InstanceId mInstanceId; // Not final, see setInstanceId()
+ /**
+ * @deprecated This field is only used when
+ * {@link enablePerDisplayPackageContextCacheInStatusbarNotif}
+ * is disabled.
+ */
+ @Deprecated
private Context mContext; // used for inflation & icon expansion
+ // Maps display id to context used for remote view content inflation and status bar icon.
+ private final Map<Integer, Context> mContextForDisplayId = new ArrayMap<>();
/** @hide */
public StatusBarNotification(String pkg, String opPkg, int id,
@@ -453,7 +465,11 @@ public class StatusBarNotification implements Parcelable {
* @hide
*/
public void clearPackageContext() {
- mContext = null;
+ if (enablePerDisplayPackageContextCacheInStatusbarNotif()) {
+ mContextForDisplayId.clear();
+ } else {
+ mContext = null;
+ }
}
/**
@@ -475,21 +491,42 @@ public class StatusBarNotification implements Parcelable {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Context getPackageContext(Context context) {
- if (mContext == null) {
- try {
- ApplicationInfo ai = context.getPackageManager()
- .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
- getNormalizedUserId());
- mContext = context.createApplicationContext(ai,
- Context.CONTEXT_RESTRICTED);
- } catch (PackageManager.NameNotFoundException e) {
- mContext = null;
+ if (enablePerDisplayPackageContextCacheInStatusbarNotif()) {
+ if (context == null) return null;
+ return mContextForDisplayId.computeIfAbsent(context.getDisplayId(),
+ (displayId) -> createPackageContext(context));
+ } else {
+ if (mContext == null) {
+ try {
+ ApplicationInfo ai = context.getPackageManager()
+ .getApplicationInfoAsUser(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES,
+ getNormalizedUserId());
+ mContext = context.createApplicationContext(ai,
+ Context.CONTEXT_RESTRICTED);
+ } catch (PackageManager.NameNotFoundException e) {
+ mContext = null;
+ }
+ }
+ if (mContext == null) {
+ mContext = context;
}
+ return mContext;
}
- if (mContext == null) {
- mContext = context;
+ }
+
+ private Context createPackageContext(Context context) {
+ try {
+ Trace.beginSection("StatusBarNotification#createPackageContext");
+ ApplicationInfo ai = context.getPackageManager()
+ .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
+ getNormalizedUserId());
+ return context.createApplicationContext(ai, Context.CONTEXT_RESTRICTED);
+ } catch (PackageManager.NameNotFoundException e) {
+ return context;
+ } finally {
+ Trace.endSection();
}
- return mContext;
}
/**
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
index eea93b321e47..2e661b475dc9 100644
--- a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
@@ -34,8 +34,7 @@ import java.lang.annotation.RetentionPolicy;
* This objects represents a value that can be used for a particular settings preference.
* <p>The data type for the value will correspond to {@link #getType}. For possible types, see
* constants below, such as {@link #TYPE_BOOLEAN} and {@link #TYPE_STRING}.
- * Depending on the type, the corresponding getter will contain its value. All other getters will
- * return default values (boolean returns false, String returns null) so they should not be used.
+ * Depending on the type, the corresponding getter will contain its value.
* <p>See documentation on the constants for which getter method should be used.
*/
@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
@@ -43,12 +42,7 @@ public final class SettingsPreferenceValue implements Parcelable {
@Type
private final int mType;
- private final boolean mBooleanValue;
- private final int mIntValue;
- private final long mLongValue;
- private final double mDoubleValue;
- @Nullable
- private final String mStringValue;
+ private final @Nullable Object mValue;
/**
* Returns the type indicator for Preference value.
@@ -59,39 +53,39 @@ public final class SettingsPreferenceValue implements Parcelable {
}
/**
- * Returns the boolean value for Preference if type is {@link #TYPE_BOOLEAN}.
+ * Returns the boolean value for Preference, the type must be {@link #TYPE_BOOLEAN}.
*/
public boolean getBooleanValue() {
- return mBooleanValue;
+ return (boolean) mValue;
}
/**
- * Returns the int value for Preference if type is {@link #TYPE_INT}.
+ * Returns the int value for Preference, the type must be {@link #TYPE_INT}.
*/
public int getIntValue() {
- return mIntValue;
+ return (int) mValue;
}
/**
- * Returns the long value for Preference if type is {@link #TYPE_LONG}.
+ * Returns the long value for Preference, the type must be {@link #TYPE_LONG}.
*/
public long getLongValue() {
- return mLongValue;
+ return (long) mValue;
}
/**
- * Returns the double value for Preference if type is {@link #TYPE_DOUBLE}.
+ * Returns the double value for Preference, the type must be {@link #TYPE_DOUBLE}.
*/
public double getDoubleValue() {
- return mDoubleValue;
+ return (double) mValue;
}
/**
- * Returns the string value for Preference if type is {@link #TYPE_STRING}.
+ * Returns the string value for Preference, the type must be {@link #TYPE_STRING}.
*/
@Nullable
public String getStringValue() {
- return mStringValue;
+ return (String) mValue;
}
/** @hide */
@@ -115,34 +109,47 @@ public final class SettingsPreferenceValue implements Parcelable {
public static final int TYPE_STRING = 3;
/** Value is of type int. Access via {@link #getIntValue}. */
public static final int TYPE_INT = 4;
+ /** Max type value. */
+ private static final int MAX_TYPE_VALUE = TYPE_INT;
private SettingsPreferenceValue(@NonNull Builder builder) {
mType = builder.mType;
- mBooleanValue = builder.mBooleanValue;
- mLongValue = builder.mLongValue;
- mDoubleValue = builder.mDoubleValue;
- mStringValue = builder.mStringValue;
- mIntValue = builder.mIntValue;
+ mValue = builder.mValue;
}
private SettingsPreferenceValue(@NonNull Parcel in) {
mType = in.readInt();
- mBooleanValue = in.readBoolean();
- mLongValue = in.readLong();
- mDoubleValue = in.readDouble();
- mStringValue = in.readString8();
- mIntValue = in.readInt();
+ if (mType == TYPE_BOOLEAN) {
+ mValue = in.readBoolean();
+ } else if (mType == TYPE_LONG) {
+ mValue = in.readLong();
+ } else if (mType == TYPE_DOUBLE) {
+ mValue = in.readDouble();
+ } else if (mType == TYPE_STRING) {
+ mValue = in.readString();
+ } else if (mType == TYPE_INT) {
+ mValue = in.readInt();
+ } else {
+ // throw exception immediately, further read to Parcel may be invalid
+ throw new IllegalStateException("Unknown type: " + mType);
+ }
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
- dest.writeBoolean(mBooleanValue);
- dest.writeLong(mLongValue);
- dest.writeDouble(mDoubleValue);
- dest.writeString8(mStringValue);
- dest.writeInt(mIntValue);
+ if (mType == TYPE_BOOLEAN) {
+ dest.writeBoolean(getBooleanValue());
+ } else if (mType == TYPE_LONG) {
+ dest.writeLong(getLongValue());
+ } else if (mType == TYPE_DOUBLE) {
+ dest.writeDouble(getDoubleValue());
+ } else if (mType == TYPE_STRING) {
+ dest.writeString(getStringValue());
+ } else if (mType == TYPE_INT) {
+ dest.writeInt(getIntValue());
+ }
}
/** @hide */
@@ -174,17 +181,16 @@ public final class SettingsPreferenceValue implements Parcelable {
public static final class Builder {
@Type
private final int mType;
- private boolean mBooleanValue;
- private long mLongValue;
- private double mDoubleValue;
- private String mStringValue;
- private int mIntValue;
+ private @Nullable Object mValue;
/**
* Create Builder instance.
* @param type type indicator for preference value
*/
public Builder(@Type int type) {
+ if (type < 0 || type > MAX_TYPE_VALUE) {
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
mType = type;
}
@@ -194,7 +200,8 @@ public final class SettingsPreferenceValue implements Parcelable {
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setBooleanValue(boolean booleanValue) {
- mBooleanValue = booleanValue;
+ checkType(TYPE_BOOLEAN);
+ mValue = booleanValue;
return this;
}
@@ -203,7 +210,8 @@ public final class SettingsPreferenceValue implements Parcelable {
*/
@NonNull
public Builder setIntValue(int intValue) {
- mIntValue = intValue;
+ checkType(TYPE_INT);
+ mValue = intValue;
return this;
}
@@ -212,7 +220,8 @@ public final class SettingsPreferenceValue implements Parcelable {
*/
@NonNull
public Builder setLongValue(long longValue) {
- mLongValue = longValue;
+ checkType(TYPE_LONG);
+ mValue = longValue;
return this;
}
@@ -221,7 +230,8 @@ public final class SettingsPreferenceValue implements Parcelable {
*/
@NonNull
public Builder setDoubleValue(double doubleValue) {
- mDoubleValue = doubleValue;
+ checkType(TYPE_DOUBLE);
+ mValue = doubleValue;
return this;
}
@@ -230,10 +240,17 @@ public final class SettingsPreferenceValue implements Parcelable {
*/
@NonNull
public Builder setStringValue(@Nullable String stringValue) {
- mStringValue = stringValue;
+ checkType(TYPE_STRING);
+ mValue = stringValue;
return this;
}
+ private void checkType(int type) {
+ if (mType != type) {
+ throw new IllegalArgumentException("Type is: " + mType);
+ }
+ }
+
/**
* Constructs an immutable {@link SettingsPreferenceValue} object.
*/
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 3c53506990d1..323d83b92143 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1066,6 +1066,12 @@ public abstract class Layout {
var hasBgColorChanged = newBackground != bgPaint.getColor();
if (lineNum != mLastLineNum || hasBgColorChanged) {
+ // Skip processing if the character is a space or a tap to avoid
+ // rendering an abrupt, empty rectangle.
+ if (Character.isWhitespace(mText.charAt(index))) {
+ return;
+ }
+
// Draw what we have so far, then reset the rect and update its color
drawRect();
mLineBackground.set(left, top, right, bottom);
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 6f7660a0fb3d..9d0ea547e734 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -161,6 +161,16 @@ public final class TaskFragmentOperation implements Parcelable {
*/
public static final int OP_TYPE_SET_PINNED = 19;
+ /**
+ * Sets whether this TaskFragment can affect system UI flags such as the status bar. Default
+ * is {@code true}.
+ *
+ * This operation is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS = 20;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -183,6 +193,7 @@ public final class TaskFragmentOperation implements Parcelable {
OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
OP_TYPE_SET_PINNED,
+ OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index be0b4fea459c..b4e7675402b9 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -463,6 +463,13 @@ flag {
}
flag {
+ name: "enable_connected_displays_pip"
+ namespace: "lse_desktop_experience"
+ description: "Enables PiP features in connected displays."
+ bug: "362721131"
+}
+
+flag {
name: "reparent_window_token_api"
namespace: "lse_desktop_experience"
description: "Allows to reparent a window token to a different display"
@@ -531,6 +538,16 @@ flag {
}
flag {
+ name: "enable_per_display_package_context_cache_in_statusbar_notif"
+ namespace: "lse_desktop_experience"
+ description: "Enables per-display package context caching in StatusBarNotification"
+ bug: "388886443"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_desktop_wallpaper_activity_for_system_user"
namespace: "lse_desktop_experience"
description: "Enables starting DesktopWallpaperActivity on system user."
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7a1078f8718f..409c3cd569ab 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -432,3 +432,12 @@ flag {
bug: "384976265"
}
+flag {
+ name: "check_disabled_snapshots_in_task_persister"
+ namespace: "windowing_frontend"
+ description: "Check for TaskSnapshots disabling in TaskSnapshotPersister."
+ bug: "387915176"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index cf3a54b8437f..b187eb42366f 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -181,7 +181,8 @@ public final class AccessibilityTargetHelper {
final InvisibleToggleAllowListingFeatureTarget magnification =
new InvisibleToggleAllowListingFeatureTarget(context,
shortcutType,
- isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
+ isShortcutContained(
+ context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
MAGNIFICATION_CONTROLLER_NAME,
uid,
context.getString(R.string.accessibility_magnification_chooser_text),
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 14ca0f8cae69..fc3c48d19b68 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -25,6 +25,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
@@ -157,19 +158,42 @@ public final class ShortcutUtils {
}
/**
- * Returns if a {@code shortcutType} shortcut contains {@code componentId}.
+ * Returns if a {@code shortcutType} shortcut contains {@code componentName}.
*
* @param context The current context.
* @param shortcutType The preferred shortcut type user selected.
- * @param componentId The component id that need to be checked.
- * @return {@code true} if a component id is contained.
+ * @param componentName The component that need to be checked.
+ * @return {@code true} if the shortcut contains {@code componentName}.
*/
- public static boolean isShortcutContained(Context context, @UserShortcutType int shortcutType,
- @NonNull String componentId) {
- final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- final List<String> requiredTargets = am.getAccessibilityShortcutTargets(shortcutType);
- return requiredTargets.contains(componentId);
+ @SuppressLint("MissingPermission")
+ public static boolean isShortcutContained(
+ Context context, @UserShortcutType int shortcutType, @NonNull String componentName) {
+ AccessibilityManager manager = context.getSystemService(AccessibilityManager.class);
+ if (manager != null) {
+ return manager
+ .getAccessibilityShortcutTargets(shortcutType).contains(componentName);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns every shortcut type that currently has the provided componentName as a target.
+ * Types are returned as a singular flag integer.
+ * If none have the componentName, returns {@link UserShortcutType#DEFAULT}
+ */
+ public static int getEnabledShortcutTypes(
+ Context context, String componentName) {
+ final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+ if (am == null) return DEFAULT;
+
+ int shortcutTypes = DEFAULT;
+ for (int shortcutType : USER_SHORTCUT_TYPES) {
+ if (am.getAccessibilityShortcutTargets(shortcutType).contains(componentName)) {
+ shortcutTypes |= shortcutType;
+ }
+ }
+ return shortcutTypes;
}
/**
@@ -229,8 +253,7 @@ public final class ShortcutUtils {
*/
public static void updateInvisibleToggleAccessibilityServiceEnableState(
Context context, Set<String> componentNames, int userId) {
- final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
+ final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
if (am == null) return;
final List<AccessibilityServiceInfo> installedServices =
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index d1adfc95461d..158b526cb1a0 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -257,8 +257,16 @@ public class Cuj {
*/
public static final int CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU = 120;
+ /** Track Launcher Overview Task Dismiss animation.
+ *
+ * <p>Tracking starts when the overview task is dismissed via
+ * {@link com.android.quickstep.views.RecentsView#dismissTask}. Tracking finishes when the
+ * animation to dismiss the overview task ends.
+ */
+ public static final int CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS = 121;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS;
/** @hide */
@IntDef({
@@ -370,7 +378,8 @@ public class Cuj {
CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
CUJ_DESKTOP_MODE_SNAP_RESIZE,
CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW,
- CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU
+ CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU,
+ CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -493,6 +502,7 @@ public class Cuj {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_SNAP_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_SNAP_RESIZE;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OVERVIEW_TASK_DISMISS;
}
private Cuj() {
@@ -729,6 +739,8 @@ public class Cuj {
return "DESKTOP_MODE_UNMAXIMIZE_WINDOW";
case CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU:
return "DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU";
+ case CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS:
+ return "LAUNCHER_OVERVIEW_TASK_DISMISS";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 1709ca78af4b..f6de3459a1f5 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -174,7 +174,9 @@ public class RuntimeInit {
// System process is dead; ignore
} else {
try {
- Clog_e(TAG, "Error reporting crash", t2);
+ // Log original crash and then log the error reporting exception.
+ Clog_e(TAG, "Couldn't report crash. Here's the crash:", e);
+ Clog_e(TAG, "Error reporting crash. Here's the error:", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ed021b64f7a0..445080215017 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8804,22 +8804,6 @@
<permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
android:protectionLevel="signature"/>
- <!--
- This permission allows the system to receive PACKAGE_CHANGED broadcasts when the component
- state of a non-exported component has been changed.
- <p>Not for use by third-party applications. </p>
- <p>Protection level: internal
- @hide
- -->
- <permission
- android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
- android:protectionLevel="internal"
- android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
-
- <uses-permission
- android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
- android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
-
<!-- @SystemApi
@FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing")
This permission is required when accessing information related to
@@ -9272,6 +9256,11 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.ZramMaintenance"
+ android:exported="false"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service android:name="com.android.server.ZramWriteback"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 3d6e1225bd92..18ba6a16bf72 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -19,6 +19,7 @@ package android.app;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
@@ -269,8 +270,9 @@ public class NotificationManagerTest {
// It doesn't matter what the returned contents are, as long as we return a channel.
// This setup must set up getNotificationChannels(), as that's the method called.
- when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
- anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
+ when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
+ anyInt(), anyBoolean())).thenReturn(
+ new ParceledListSlice<>(List.of(exampleChannel())));
// ask for the same channel 100 times without invalidating the cache
for (int i = 0; i < 100; i++) {
@@ -282,7 +284,7 @@ public class NotificationManagerTest {
NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
verify(mNotificationManager.mBackendService, times(2))
- .getNotificationChannels(any(), any(), anyInt());
+ .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
}
@Test
@@ -295,23 +297,24 @@ public class NotificationManagerTest {
NotificationChannel c2 = new NotificationChannel("id2", "name2",
NotificationManager.IMPORTANCE_NONE);
- when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
- anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, c2)));
+ when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
+ anyInt(), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(c1, c2)));
assertThat(mNotificationManager.getNotificationChannel("id1")).isEqualTo(c1);
assertThat(mNotificationManager.getNotificationChannel("id2")).isEqualTo(c2);
assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
verify(mNotificationManager.mBackendService, times(1))
- .getNotificationChannels(any(), any(), anyInt());
+ .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
}
@Test
@EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
public void getNotificationChannels_cachedUntilInvalidated() throws Exception {
NotificationManager.invalidateNotificationChannelCache();
- when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
- anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
+ when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
+ anyInt(), anyBoolean())).thenReturn(
+ new ParceledListSlice<>(List.of(exampleChannel())));
// ask for channels 100 times without invalidating the cache
for (int i = 0; i < 100; i++) {
@@ -323,7 +326,7 @@ public class NotificationManagerTest {
List<NotificationChannel> res = mNotificationManager.getNotificationChannels();
verify(mNotificationManager.mBackendService, times(2))
- .getNotificationChannels(any(), any(), anyInt());
+ .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
assertThat(res).containsExactlyElementsIn(List.of(exampleChannel()));
}
@@ -341,8 +344,9 @@ public class NotificationManagerTest {
NotificationChannel c2 = new NotificationChannel("other", "name2",
NotificationManager.IMPORTANCE_DEFAULT);
- when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), anyInt()))
- .thenReturn(new ParceledListSlice<>(List.of(c1, conv1, c2)));
+ when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
+ anyInt(), anyBoolean())).thenReturn(
+ new ParceledListSlice<>(List.of(c1, conv1, c2)));
// Lookup for channel c1 and c2: returned as expected
assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(c1);
@@ -359,9 +363,9 @@ public class NotificationManagerTest {
// Lookup of a nonexistent channel is null
assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
- // All of that should have been one call to getNotificationChannels()
+ // All of that should have been one call to getOrCreateNotificationChannels()
verify(mNotificationManager.mBackendService, times(1))
- .getNotificationChannels(any(), any(), anyInt());
+ .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
}
@Test
@@ -381,12 +385,12 @@ public class NotificationManagerTest {
NotificationChannel channel3 = channel1.copy();
channel3.setName("name3");
- when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
- eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel1)));
- when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg2),
- eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel2)));
- when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
- eq(userId1))).thenReturn(new ParceledListSlice<>(List.of(channel3)));
+ when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg1),
+ eq(userId), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel1)));
+ when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg2),
+ eq(userId), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel2)));
+ when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg1),
+ eq(userId1), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel3)));
// set our context to pretend to be from package 1 and userId 0
mContext.setParameters(pkg1, pkg1, userId);
@@ -402,7 +406,7 @@ public class NotificationManagerTest {
// Those should have been three different calls
verify(mNotificationManager.mBackendService, times(3))
- .getNotificationChannels(any(), any(), anyInt());
+ .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
}
private Notification exampleNotification() {
diff --git a/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java b/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java
index e093e1af2eb5..1f9d4278b12e 100644
--- a/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java
+++ b/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java
@@ -26,6 +26,7 @@ import android.hardware.power.CpuHeadroomResult;
import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.SupportInfo;
import android.os.health.SystemHealthManager;
+import android.platform.test.annotations.DisabledOnRavenwood;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +39,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = SystemHealthManager.class)
public class SystemHealthManagerUnitTest {
@Mock
private IBatteryStats mBatteryStats;
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index 504240812559..7a4cc7f98a1a 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -22,6 +22,7 @@ import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -35,14 +36,19 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.metrics.LogMaker;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.Display;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.window.flags.Flags;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -52,11 +58,16 @@ import org.mockito.MockitoAnnotations;
@SmallTest
public class StatusBarNotificationTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final Context mMockContext = mock(Context.class);
@Mock
private Context mRealContext;
@Mock
private PackageManager mPm;
+ @Mock
+ private Context mSecondaryDisplayContext;
private static final String PKG = "com.example.o";
private static final int UID = 9583;
@@ -80,6 +91,10 @@ public class StatusBarNotificationTest {
InstrumentationRegistry.getContext().getResources());
when(mMockContext.getPackageManager()).thenReturn(mPm);
when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ when(mMockContext.getDisplayId()).thenReturn(Display.DEFAULT_DISPLAY);
+ when(mSecondaryDisplayContext.getPackageManager()).thenReturn(mPm);
+ when(mSecondaryDisplayContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ when(mSecondaryDisplayContext.getDisplayId()).thenReturn(2);
when(mPm.getApplicationLabel(any())).thenReturn("");
mRealContext = InstrumentationRegistry.getContext();
@@ -221,6 +236,24 @@ public class StatusBarNotificationTest {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF)
+ public void testGetPackageContext_multipleDisplaysCase() {
+ String pkg = "com.android.systemui";
+ int uid = 1000;
+ Notification notification = getNotificationBuilder(GROUP_ID_1, CHANNEL_ID).build();
+ StatusBarNotification sbn = new StatusBarNotification(
+ pkg, pkg, ID, TAG, uid, uid, notification, UserHandle.ALL, null, UID);
+ Context defaultContext = sbn.getPackageContext(mRealContext);
+ Context secondaryContext = sbn.getPackageContext(mSecondaryDisplayContext);
+ assertNotSame(mRealContext, defaultContext);
+ assertNotSame(defaultContext, secondaryContext);
+
+ // Let's make sure it caches it:
+ assertSame(defaultContext, sbn.getPackageContext(mRealContext));
+ assertSame(secondaryContext, sbn.getPackageContext(mSecondaryDisplayContext));
+ }
+
+ @Test
public void testGetUidFromKey() {
StatusBarNotification sbn = getNotification("pkg", null, "channel");
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index c7d85d4b9b76..9e78af57b470 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -1024,6 +1024,55 @@ public class LayoutTest {
expect.that(backgroundCommands.size()).isEqualTo(backgroundRectsDrawn);
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testWhitespaceText_DrawsBackgroundsWithAdjacentLetters() {
+ mTextPaint.setColor(Color.BLACK);
+ SpannableString spannedText = new SpannableString("Test\tTap and Space");
+
+ // Set the entire text to white initially
+ spannedText.setSpan(
+ new ForegroundColorSpan(Color.WHITE),
+ /* start= */ 0,
+ /* end= */ spannedText.length(),
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+ );
+
+ // Find the whitespace character and set its color to black
+ for (int i = 0; i < spannedText.length(); i++) {
+ if (Character.isWhitespace(spannedText.charAt(i))) {
+ spannedText.setSpan(
+ new ForegroundColorSpan(Color.BLACK),
+ i,
+ i + 1,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+ );
+ }
+ }
+
+ Layout layout = new StaticLayout(spannedText, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+ MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(
+ c,
+ /* highlightPaths= */ null,
+ /* highlightPaints= */ null,
+ /* selectionPath= */ null,
+ /* selectionPaint= */ null,
+ /* cursorOffsetVertical= */ 0
+ );
+
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+ if (drawCommand.rect != null) {
+ expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.BLACK);
+ }
+ }
+ }
+
private int removeAlpha(int color) {
return Color.rgb(
Color.red(color),
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
index 1a9af6b55eed..d1555e389c2d 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
@@ -20,8 +20,8 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATI
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.google.common.truth.Truth.assertThat;
@@ -42,19 +42,22 @@ import android.testing.TestableContext;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.TestUtils;
import com.android.internal.accessibility.common.ShortcutConstants;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
@@ -62,7 +65,7 @@ import java.util.StringJoiner;
/**
* Unit Tests for {@link com.android.internal.accessibility.util.ShortcutUtils}
*/
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
public class ShortcutUtilsTest {
private static final Set<String> ONE_COMPONENT = Set.of(
new ComponentName("pkg", "serv").flattenToString());
@@ -99,38 +102,19 @@ public class ShortcutUtilsTest {
}
@Test
- public void getShortcutTargets_softwareShortcutNoService_emptyResult() {
- assertThat(
- ShortcutUtils.getShortcutTargetsFromSettings(
- mContext, SOFTWARE, mDefaultUserId)
- ).isEmpty();
- }
-
- @Test
- public void getShortcutTargets_volumeKeyShortcutNoService_emptyResult() {
- assertThat(
- ShortcutUtils.getShortcutTargetsFromSettings(
- mContext, ShortcutConstants.UserShortcutType.HARDWARE,
- mDefaultUserId)
- ).isEmpty();
- }
-
- @Test
- public void getShortcutTargets_gestureShortcutNoService_emptyResult() {
- assertThat(
- ShortcutUtils.getShortcutTargetsFromSettings(
- mContext, GESTURE, mDefaultUserId)
- ).isEmpty();
- }
+ public void getShortcutTargets_noService_emptyResult(
+ @TestParameter(valuesProvider = ShortcutTypeValueProvider.class) int shortcutType) {
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ ShortcutUtils.convertToKey(shortcutType), "", mContext.getUserId());
- @Test
- public void getShortcutTargets_keyGestureShortcutNoService_emptyResult() {
assertThat(
ShortcutUtils.getShortcutTargetsFromSettings(
- mContext, KEY_GESTURE, mDefaultUserId)
+ mContext, shortcutType, mDefaultUserId)
).isEmpty();
}
+ // TODO 385186274: Parameterize this test.
@Test
public void getShortcutTargets_softwareShortcut1Service_return1Service() {
setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
@@ -143,6 +127,7 @@ public class ShortcutUtilsTest {
).containsExactlyElementsIn(ONE_COMPONENT);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void getShortcutTargets_volumeShortcut2Service_return2Service() {
setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
@@ -155,6 +140,7 @@ public class ShortcutUtilsTest {
).containsExactlyElementsIn(TWO_COMPONENTS);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void getShortcutTargets_tripleTapShortcut_magnificationDisabled_emptyResult() {
enableTripleTapShortcutForMagnification(/* enable= */ false);
@@ -168,6 +154,7 @@ public class ShortcutUtilsTest {
).isEmpty();
}
+ // TODO 385186274: Parameterize this test.
@Test
public void getShortcutTargets_tripleTapShortcut_magnificationEnabled_returnMagnification() {
setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
@@ -181,6 +168,7 @@ public class ShortcutUtilsTest {
).containsExactly(ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOn_noShortcuts_serviceTurnedOff() {
setupA11yServiceAndShortcutState(
@@ -195,6 +183,7 @@ public class ShortcutUtilsTest {
assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ false);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOnForBothUsers_noShortcutsForGuestUser_serviceTurnedOffForGuestUserOnly() {
// setup arbitrary userId by add 10 to the default user id
@@ -218,6 +207,7 @@ public class ShortcutUtilsTest {
ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true, mDefaultUserId);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOn_hasShortcut_serviceKeepsOn() {
setupA11yServiceAndShortcutState(
@@ -232,6 +222,7 @@ public class ShortcutUtilsTest {
assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOff_noShortcuts_serviceKeepsOff() {
setupA11yServiceAndShortcutState(
@@ -246,6 +237,7 @@ public class ShortcutUtilsTest {
assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ false);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOff_hasShortcuts_serviceTurnsOn() {
setupA11yServiceAndShortcutState(
@@ -260,6 +252,7 @@ public class ShortcutUtilsTest {
assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_standardA11yServiceOn_noShortcuts_serviceKeepsOn() {
setupA11yServiceAndShortcutState(
@@ -274,6 +267,7 @@ public class ShortcutUtilsTest {
assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ true);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_standardA11yServiceOn_hasShortcuts_serviceKeepsOn() {
setupA11yServiceAndShortcutState(
@@ -288,6 +282,7 @@ public class ShortcutUtilsTest {
assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ true);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_standardA11yServiceOff_noShortcuts_serviceKeepsOff() {
setupA11yServiceAndShortcutState(
@@ -302,6 +297,7 @@ public class ShortcutUtilsTest {
assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ false);
}
+ // TODO 385186274: Parameterize this test.
@Test
public void updateAccessibilityServiceStateIfNeeded_standardA11yServiceOff_hasShortcuts_serviceKeepsOff() {
setupA11yServiceAndShortcutState(
@@ -316,6 +312,37 @@ public class ShortcutUtilsTest {
assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ false);
}
+ @Test
+ public void getEnabledShortcutTypes_oneShortcut_returnsExpectedType(
+ @TestParameter(valuesProvider = ShortcutTypeValueProvider.class) int shortcutType)
+ throws RemoteException {
+ clearMockShortcutTypes();
+ assertThat(ShortcutUtils.getEnabledShortcutTypes(
+ mContext, STANDARD_SERVICE_COMPONENT_NAME)).isEqualTo(DEFAULT);
+ mockShortcutType(shortcutType, STANDARD_SERVICE_COMPONENT_NAME);
+ assertThat(ShortcutUtils.getEnabledShortcutTypes(
+ mContext, STANDARD_SERVICE_COMPONENT_NAME)).isEqualTo(shortcutType);
+
+ }
+
+ @Test
+ public void getEnabledShortcutTypes_twoShortcuts_returnsExpectedTypes(
+ @TestParameter(valuesProvider = ShortcutTypeValueProvider.class) int shortcutType1,
+ @TestParameter(valuesProvider = ShortcutTypeValueProvider.class) int shortcutType2
+ ) throws RemoteException {
+ if (shortcutType1 == shortcutType2) {
+ return;
+ }
+ clearMockShortcutTypes();
+ assertThat(ShortcutUtils.getEnabledShortcutTypes(
+ mContext, STANDARD_SERVICE_COMPONENT_NAME)).isEqualTo(DEFAULT);
+ mockShortcutType(shortcutType1, STANDARD_SERVICE_COMPONENT_NAME);
+ mockShortcutType(shortcutType2, STANDARD_SERVICE_COMPONENT_NAME);
+ assertThat(ShortcutUtils.getEnabledShortcutTypes(
+ mContext, STANDARD_SERVICE_COMPONENT_NAME)).isEqualTo(
+ shortcutType1 | shortcutType2);
+ }
+
private void setupShortcutTargets(Set<String> components, String shortcutSettingsKey) {
final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
for (String target : components) {
@@ -403,4 +430,29 @@ public class ShortcutUtilsTest {
add ? a11yServiceComponentName : "",
userId);
}
+
+ private void clearMockShortcutTypes() throws RemoteException {
+ for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+ when(mAccessibilityManagerService
+ .getAccessibilityShortcutTargets(shortcutType)).thenReturn(List.of());
+ }
+ }
+
+ private void mockShortcutType(int shortcutType, String componentName)
+ throws RemoteException {
+ when(mAccessibilityManagerService.getAccessibilityShortcutTargets(shortcutType))
+ .thenReturn(List.of(componentName));
+ }
+
+ static final class ShortcutTypeValueProvider implements
+ TestParameter.TestParameterValuesProvider {
+ @Override
+ public List<Integer> provideValues() {
+ List<Integer> values = new ArrayList<>();
+ for (int shortcutType: USER_SHORTCUT_TYPES) {
+ values.add(shortcutType);
+ }
+ return values;
+ }
+ }
}
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index 3403bbfa2384..3b4014867ef7 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -102,7 +102,7 @@ Changes to the whitelist during system updates can result in installing addition
to pre-existing users, but cannot uninstall pre-existing system packages from pre-existing users.
-->
<config>
- <!-- Bluetooth (com.android.btservices apex) - visible on the sharesheet -->
+ <!-- Bluetooth (com.android.bt apex) - visible on the sharesheet -->
<install-in-user-type package="com.android.bluetooth">
<install-in user-type="SYSTEM" />
<install-in user-type="FULL" />
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index e6c652c14c71..5e93f8db9388 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,6 +20,7 @@ import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MG
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyParameter;
@@ -732,6 +733,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
}
+ @RequiresPermission(value = android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ conditional = true)
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
@@ -824,7 +827,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
break;
}
case AttestationUtils.ID_TYPE_MEID: {
- final String meid = telephonyService.getMeid(0);
+ String meid;
+ try {
+ meid = telephonyService.getMeid(0);
+ } catch (UnsupportedOperationException e) {
+ Log.e(TAG, "Unable to retrieve MEID", e);
+ meid = null;
+ }
if (meid == null) {
throw new DeviceIdAttestationException("Unable to retrieve MEID");
}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 957d1b835ec2..bcb6c4f555f7 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -170,9 +170,9 @@ android_library {
"res",
],
static_libs: [
+ "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:iconloader_base",
- "//packages/apps/Car/SystemUI/aconfig:com_android_systemui_car_flags_lib",
"PlatformAnimationLib",
"WindowManager-Shell-lite-proto",
"WindowManager-Shell-proto",
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 4f1cd9780f8b..9c15319585b7 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -32,7 +32,7 @@
<activity
android:name=".desktopmode.DesktopWallpaperActivity"
android:excludeFromRecents="true"
- android:launchMode="singleInstance"
+ android:launchMode="singleInstancePerTask"
android:showForAllUsers="true"
android:theme="@style/DesktopWallpaperTheme" />
diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp
index 7f8f57b172ff..f8da7fa86cff 100644
--- a/libs/WindowManager/Shell/aconfig/Android.bp
+++ b/libs/WindowManager/Shell/aconfig/Android.bp
@@ -4,6 +4,7 @@ aconfig_declarations {
container: "system",
srcs: [
"multitasking.aconfig",
+ "automotive.aconfig",
],
}
diff --git a/libs/WindowManager/Shell/aconfig/automotive.aconfig b/libs/WindowManager/Shell/aconfig/automotive.aconfig
new file mode 100644
index 000000000000..2f25aa460ec1
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/automotive.aconfig
@@ -0,0 +1,11 @@
+# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
+
+package: "com.android.wm.shell"
+container: "system"
+
+flag {
+ name: "enable_auto_task_stack_controller"
+ namespace: "multitasking"
+ description: "Enables auto task stack controller to manage task stacks on automotive"
+ bug: "384082238"
+}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 688bf83224aa..b10b099c970b 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -13,13 +13,6 @@ flag {
}
flag {
- name: "enable_split_contextual"
- namespace: "multitasking"
- description: "Enables invoking split contextually"
- bug: "276361926"
-}
-
-flag {
name: "enable_taskbar_navbar_unification"
namespace: "multitasking"
description: "Enables taskbar / navbar unification"
@@ -104,6 +97,13 @@ flag {
}
flag {
+ name: "enable_create_any_bubble"
+ namespace: "multitasking"
+ description: "Enable UI affordances to create bubbles via launcher app icons"
+ bug: "385220199"
+}
+
+flag {
name: "only_reuse_bubbled_task_when_launched_from_bubble"
namespace: "multitasking"
description: "Allow reusing bubbled tasks for new activities only when launching from bubbles"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
index 7347fbad5f5d..fc8b29912955 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
android:maxWidth="@dimen/bubble_popup_content_max_width"
- android:maxLines="1"
+ android:maxLines="2"
android:ellipsize="end"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
android:textColor="@androidprv:color/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
index f0e1871168dd..1616707954f5 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
android:maxWidth="@dimen/bubble_popup_content_max_width"
- android:maxLines="1"
+ android:maxLines="2"
android:ellipsize="end"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
android:textColor="@androidprv:color/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
index 82ef00e46e8c..10023c9dba40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.appzoomout;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.Flags.spatialModelAppPushback;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
@@ -93,7 +94,9 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
mDisplayAreaOrganizer = displayAreaOrganizer;
mMainExecutor = mainExecutor;
- shellInit.addInitCallback(this::onInit, this);
+ if (spatialModelAppPushback()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
private void onInit() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
index f8f284238a98..8171312762ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
@@ -33,7 +33,7 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
-import com.android.systemui.car.Flags.autoTaskStackWindowing
+import com.android.wm.shell.Flags.enableAutoTaskStackController
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.ShellExecutor
@@ -66,7 +66,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>()
init {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
throw IllegalStateException("Failed to initialize" +
"AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.")
} else {
@@ -220,7 +220,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
displayId: Int,
listener: RootTaskStackListener
) {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to create root task stack as the " +
"auto_task_stack_windowing TS flag is disabled."
@@ -236,7 +236,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
}
override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to set default root task stack as the " +
"auto_task_stack_windowing TS flag is disabled."
@@ -280,7 +280,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
}
override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to start transaction as the " +
"auto_task_stack_windowing TS flag is disabled."
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 862906a11424..62995319db80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -136,23 +136,15 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
// Update bitmap
val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
- bitmap =
- iconFactory
- .createBadgedIconBitmap(AdaptiveIconDrawable(ColorDrawable(colorAccent), fg))
- .icon
+ val drawable = AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)
+ bitmap = iconFactory.createBadgedIconBitmap(drawable).icon
// Update dot path
dotPath =
PathParser.createPathFromPathData(
res.getString(com.android.internal.R.string.config_icon_mask)
)
- val scale =
- iconFactory.normalizer.getScale(
- iconView!!.iconDrawable,
- null /* outBounds */,
- null /* path */,
- null /* outMaskShape */
- )
+ val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable)
val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
val matrix = Matrix()
matrix.setScale(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index eb1e72790a25..c9890a5b4963 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -447,8 +447,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
- private int imeTop(float surfaceOffset) {
- return mImeFrame.top + (int) surfaceOffset;
+ private int imeTop(float surfaceOffset, float surfacePositionY) {
+ // surfaceOffset is already offset by the surface's top inset, so we need to subtract
+ // the top inset so that the return value is in screen coordinates.
+ return mImeFrame.top + (int) (surfaceOffset - surfacePositionY);
}
private boolean calcIsFloating(InsetsSource imeSource) {
@@ -581,7 +583,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
final float alpha = (mAnimateAlpha || isFloating)
? (value - hiddenY) / (shownY - hiddenY) : 1f;
t.setAlpha(animatingLeash, alpha);
- dispatchPositionChanged(mDisplayId, imeTop(value), t);
+ dispatchPositionChanged(mDisplayId, imeTop(value, defaultY), t);
t.apply();
mTransactionPool.release(t);
});
@@ -600,11 +602,12 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
t.setPosition(animatingLeash, x, value);
if (DEBUG) {
Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
- + imeTop(hiddenY) + "->" + imeTop(shownY)
+ + imeTop(hiddenY, defaultY) + "->" + imeTop(shownY, defaultY)
+ " showing:" + (mAnimationDirection == DIRECTION_SHOW));
}
- int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY),
- imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, isFloating, t);
+ int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY, defaultY),
+ imeTop(shownY, defaultY), mAnimationDirection == DIRECTION_SHOW,
+ isFloating, t);
mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0;
final float alpha = (mAnimateAlpha || isFloating)
? (value - hiddenY) / (shownY - hiddenY)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 73d15270c811..f38957e48dbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -552,7 +552,11 @@ class DesktopTasksController(
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
- moveHomeTask(wct, toTop = true)
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+ moveHomeTask(wct, toTop = true, taskInfo.displayId)
+ } else {
+ moveHomeTask(wct, toTop = true)
+ }
val taskIdToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
@@ -1309,11 +1313,15 @@ class DesktopTasksController(
): Int? {
logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
// Move home to front, ensures that we go back home when all desktop windows are closed
- moveHomeTask(wct, toTop = true)
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+ moveHomeTask(wct, toTop = true, displayId)
+ } else {
+ moveHomeTask(wct, toTop = true)
+ }
// Currently, we only handle the desktop on the default display really.
if (
- (displayId == DEFAULT_DISPLAY || Flags.enableBugFixesForSecondaryDisplay()) &&
+ (displayId == DEFAULT_DISPLAY || Flags.enablePerDisplayDesktopWallpaperActivity()) &&
ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
) {
// Add translucent wallpaper activity to show the wallpaper underneath
@@ -1359,9 +1367,13 @@ class DesktopTasksController(
return taskIdToMinimize
}
- private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
+ private fun moveHomeTask(
+ wct: WindowContainerTransaction,
+ toTop: Boolean,
+ displayId: Int = DEFAULT_DISPLAY,
+ ) {
shellTaskOrganizer
- .getRunningTasks(context.displayId)
+ .getRunningTasks(displayId)
.firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) }
}
@@ -1370,12 +1382,19 @@ class DesktopTasksController(
logV("addWallpaperActivity")
if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
val intent = Intent(context, DesktopWallpaperActivity::class.java)
+ if (
+ desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
+ Flags.enablePerDisplayDesktopWallpaperActivity()
+ ) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ }
val options =
ActivityOptions.makeBasic().apply {
launchWindowingMode = WINDOWING_MODE_FULLSCREEN
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
- if (Flags.enableBugFixesForSecondaryDisplay()) {
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
launchDisplayId = displayId
}
}
@@ -1391,13 +1410,20 @@ class DesktopTasksController(
val userHandle = UserHandle.of(userId)
val userContext = context.createContextAsUser(userHandle, /* flags= */ 0)
val intent = Intent(userContext, DesktopWallpaperActivity::class.java)
+ if (
+ desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
+ Flags.enablePerDisplayDesktopWallpaperActivity()
+ ) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ }
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId)
val options =
ActivityOptions.makeBasic().apply {
launchWindowingMode = WINDOWING_MODE_FULLSCREEN
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
- if (Flags.enableBugFixesForSecondaryDisplay()) {
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
launchDisplayId = displayId
}
}
@@ -1414,8 +1440,8 @@ class DesktopTasksController(
}
}
- private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
- desktopWallpaperActivityTokenProvider.getToken()?.let { token ->
+ private fun removeWallpaperActivity(wct: WindowContainerTransaction, displayId: Int) {
+ desktopWallpaperActivityTokenProvider.getToken(displayId)?.let { token ->
logV("removeWallpaperActivity")
if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
wct.reorder(token, /* onTop= */ false)
@@ -1440,9 +1466,6 @@ class DesktopTasksController(
if (!taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId)) {
return
}
- if (displayId != DEFAULT_DISPLAY) {
- return
- }
} else if (
Flags.enableDesktopWindowingPip() &&
taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
@@ -1457,7 +1480,7 @@ class DesktopTasksController(
desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
FULLSCREEN_ANIMATION_DURATION
)
- removeWallpaperActivity(wct)
+ removeWallpaperActivity(wct, displayId)
}
fun releaseVisualIndicator() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b9a65fee7d4d..14c8429766cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -60,7 +60,9 @@ class DesktopTasksTransitionObserver(
shellInit: ShellInit,
) : Transitions.TransitionObserver {
- private var transitionToCloseWallpaper: IBinder? = null
+ data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int)
+
+ private var transitionToCloseWallpaper: CloseWallpaperTransition? = null
/* Pending PiP transition and its associated display id and task id. */
private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null
private var currentProfileId: Int
@@ -248,9 +250,10 @@ class DesktopTasksTransitionObserver(
desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 0 &&
change.mode == TRANSIT_CLOSE &&
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
- desktopWallpaperActivityTokenProvider.getToken() != null
+ desktopWallpaperActivityTokenProvider.getToken(taskInfo.displayId) != null
) {
- transitionToCloseWallpaper = transition
+ transitionToCloseWallpaper =
+ CloseWallpaperTransition(transition, taskInfo.displayId)
currentProfileId = taskInfo.userId
}
}
@@ -265,25 +268,28 @@ class DesktopTasksTransitionObserver(
}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
+ val lastSeenTransitionToCloseWallpaper = transitionToCloseWallpaper
// TODO: b/332682201 Update repository state
- if (transitionToCloseWallpaper == transition) {
+ if (lastSeenTransitionToCloseWallpaper?.transition == transition) {
// TODO: b/362469671 - Handle merging the animation when desktop is also closing.
- desktopWallpaperActivityTokenProvider.getToken()?.let { wallpaperActivityToken ->
- if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
- transitions.startTransition(
- TRANSIT_TO_BACK,
- WindowContainerTransaction()
- .reorder(wallpaperActivityToken, /* onTop= */ false),
- null,
- )
- } else {
- transitions.startTransition(
- TRANSIT_CLOSE,
- WindowContainerTransaction().removeTask(wallpaperActivityToken),
- null,
- )
+ desktopWallpaperActivityTokenProvider
+ .getToken(lastSeenTransitionToCloseWallpaper.displayId)
+ ?.let { wallpaperActivityToken ->
+ if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
+ transitions.startTransition(
+ TRANSIT_TO_BACK,
+ WindowContainerTransaction()
+ .reorder(wallpaperActivityToken, /* onTop= */ false),
+ null,
+ )
+ } else {
+ transitions.startTransition(
+ TRANSIT_CLOSE,
+ WindowContainerTransaction().removeTask(wallpaperActivityToken),
+ null,
+ )
+ }
}
- }
transitionToCloseWallpaper = null
} else if (pendingPipTransitionAndPipTask?.first == transition) {
val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 6e7740dc13e3..91150b0070ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -129,6 +129,20 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
+ * Called when the Shell wants to start an exit-via-expand from Pip transition/animation.
+ */
+ public void startExpandTransition(WindowContainerTransaction out) {
+ // Default implementation does nothing.
+ }
+
+ /**
+ * Called when the Shell wants to start a remove Pip transition/animation.
+ */
+ public void startRemoveTransition(boolean withFadeout) {
+ // Default implementation does nothing.
+ }
+
+ /**
* Called when the Shell wants to start resizing Pip transition/animation.
*
* @param duration the suggested duration for resize animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 9babe9e9e4eb..e22cd37ab4b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -344,13 +344,22 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
*/
@Override
public void dismissPip() {
+ dismissPip(true /* withFadeout */);
+ }
+
+ /**
+ * Dismisses the pinned stack.
+ *
+ * @param withFadeout should animate with fadeout for the removal
+ */
+ public void dismissPip(boolean withFadeout) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
- mPipScheduler.scheduleRemovePip();
+ mPipScheduler.scheduleRemovePip(withFadeout);
}
/** Sets the movement bounds to use to constrain PIP position animations. */
@@ -473,7 +482,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2,
0,
mSpringConfig)
- .withEndActions(this::dismissPip);
+ .withEndActions(() -> dismissPip(false /* withFadeout */));
startBoundsAnimator(
getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */);
@@ -772,7 +781,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
case PipTransitionState.EXITING_PIP:
// We need to force finish any local animators if about to leave PiP, to avoid
// breaking the state (e.g. leashes are cleaned up upon exit).
- if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
cancelPhysicsAnimation();
settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index ea8dac982703..ed532cad0523 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -19,9 +19,6 @@ package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
-
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -112,19 +109,6 @@ public class PipScheduler {
return wct;
}
- @Nullable
- private WindowContainerTransaction getRemovePipTransaction() {
- WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
- if (pipTaskToken == null) {
- return null;
- }
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(pipTaskToken, null);
- wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
- wct.reorder(pipTaskToken, false);
- return wct;
- }
-
/**
* Schedules exit PiP via expand transition.
*/
@@ -133,21 +117,16 @@ public class PipScheduler {
if (!mPipTransitionState.isInPip()) return;
WindowContainerTransaction wct = getExitPipViaExpandTransaction();
if (wct != null) {
- mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct,
- null /* destinationBounds */);
+ mPipTransitionController.startExpandTransition(wct);
}
});
}
/** Schedules remove PiP transition. */
- public void scheduleRemovePip() {
+ public void scheduleRemovePip(boolean withFadeout) {
mMainExecutor.execute(() -> {
if (!mPipTransitionState.isInPip()) return;
- WindowContainerTransaction wct = getRemovePipTransaction();
- if (wct != null) {
- mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
- null /* destinationBounds */);
- }
+ mPipTransitionController.startRemoveTransition(withFadeout);
});
}
@@ -216,9 +195,11 @@ public class PipScheduler {
* @param degrees the angle to rotate the bounds to.
*/
public void scheduleUserResizePip(Rect toBounds, float degrees) {
- if (toBounds.isEmpty()) {
+ if (toBounds.isEmpty() || !mPipTransitionState.isInPip()) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
+ "%s: Attempted to user resize PIP in invalid state, aborting;"
+ + "toBounds=%s, mPipTransitionState=%s",
+ TAG, toBounds, mPipTransitionState);
return;
}
SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 0a42c71ce095..4902455cae16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -126,6 +127,7 @@ public class PipTransition extends PipTransitionController implements
@Nullable
private IBinder mResizeTransition;
private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+ private boolean mPendingRemoveWithFadeout;
//
@@ -184,15 +186,19 @@ public class PipTransition extends PipTransitionController implements
//
@Override
- public void startExitTransition(int type, WindowContainerTransaction out,
- @Nullable Rect destinationBounds) {
- if (out == null) {
- return;
- }
- IBinder transition = mTransitions.startTransition(type, out, this);
- if (type == TRANSIT_EXIT_PIP) {
- mExitViaExpandTransition = transition;
- }
+ public void startExpandTransition(WindowContainerTransaction out) {
+ if (out == null) return;
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+ mExitViaExpandTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
+ }
+
+ @Override
+ public void startRemoveTransition(boolean withFadeout) {
+ final WindowContainerTransaction wct = getRemovePipTransaction();
+ if (wct == null) return;
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+ mPendingRemoveWithFadeout = withFadeout;
+ mTransitions.startTransition(TRANSIT_REMOVE_PIP, wct, this);
}
@Override
@@ -284,7 +290,6 @@ public class PipTransition extends PipTransitionController implements
finishCallback);
} else if (transition == mExitViaExpandTransition) {
mExitViaExpandTransition = null;
- mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
} else if (transition == mResizeTransition) {
mResizeTransition = null;
@@ -690,11 +695,19 @@ public class PipTransition extends PipTransitionController implements
TransitionInfo.Change pipChange = getChangeByToken(info,
mPipTransitionState.getPipTaskToken());
mFinishCallback = finishCallback;
- PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
- startTransaction, PipAlphaAnimator.FADE_OUT);
+
finishTransaction.setAlpha(pipChange.getLeash(), 0f);
- animator.setAnimationEndCallback(this::finishTransition);
- animator.start();
+ if (mPendingRemoveWithFadeout) {
+ PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
+ startTransaction, PipAlphaAnimator.FADE_OUT);
+ animator.setAnimationEndCallback(this::finishTransition);
+ animator.start();
+ } else {
+ // Jumpcut to a faded-out PiP if no fadeout animation was requested.
+ startTransaction.setAlpha(pipChange.getLeash(), 0f);
+ startTransaction.apply();
+ finishTransition();
+ }
return true;
}
@@ -824,6 +837,19 @@ public class PipTransition extends PipTransitionController implements
return wct;
}
+ @Nullable
+ private WindowContainerTransaction getRemovePipTransaction() {
+ WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+ if (pipTaskToken == null) {
+ return null;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(pipTaskToken, null);
+ wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.reorder(pipTaskToken, false);
+ return wct;
+ }
+
private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipChange() != null
? requestInfo.getPipChange().getTaskInfo() : null;
@@ -1000,6 +1026,7 @@ public class PipTransition extends PipTransitionController implements
}
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
+ mPendingRemoveWithFadeout = false;
break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 6f9f40a487c7..8805cbb0dfbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -27,7 +27,9 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.io.PrintWriter;
@@ -201,6 +203,13 @@ public class PipTransitionState {
Preconditions.checkArgument(extra != null && !extra.isEmpty(),
"No extra bundle for " + stateToString(state) + " state.");
}
+ if (!shouldTransitionToState(state)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Attempted to transition to an invalid state=%s, while in %s",
+ TAG, stateToString(state), this);
+ return;
+ }
+
if (mState != state) {
final int prevState = mState;
mState = state;
@@ -374,6 +383,17 @@ public class PipTransitionState {
return ++mPrevCustomState;
}
+ private boolean shouldTransitionToState(@TransitionState int newState) {
+ switch (newState) {
+ case SCHEDULED_BOUNDS_CHANGE:
+ // Allow scheduling bounds change only while in PiP, except for if another bounds
+ // change was scheduled but hasn't started playing yet.
+ return isInPip();
+ default:
+ return true;
+ }
+ }
+
private static String stateToString(int state) {
switch (state) {
case UNDEFINED: return "undefined";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c27ef295db51..c9136b4ad18d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -929,8 +929,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
for (int taskId : taskIds) {
ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task != null) {
- wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
- .setBounds(task.token, null);
+ wct.setWindowingMode(task.getToken(), WINDOWING_MODE_UNDEFINED)
+ .setBounds(task.getToken(), null);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 81f444ba2af3..c5994f83429a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -633,7 +633,7 @@ public class SplashscreenContentDrawer {
private class ShapeIconFactory extends BaseIconFactory {
protected ShapeIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
- super(context, fillResIconDpi, iconBitmapSize, true /* shapeDetection */);
+ super(context, fillResIconDpi, iconBitmapSize);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9fbda46bd2b7..67dae283345a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -1969,7 +1969,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
Supplier<SurfaceControl.Transaction> transactionFactory,
Handler handler) {
final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
- ? new VeiledResizeTaskPositioner(
+ // TODO(b/383632995): Update when the flag is launched.
+ ? (Flags.enableConnectedDisplaysWindowDrag()
+ ? new MultiDisplayVeiledResizeTaskPositioner(
taskOrganizer,
windowDecoration,
displayController,
@@ -1977,6 +1979,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
transitions,
interactionJankMonitor,
handler)
+ : new VeiledResizeTaskPositioner(
+ taskOrganizer,
+ windowDecoration,
+ displayController,
+ dragEventListener,
+ transitions,
+ interactionJankMonitor,
+ handler))
: new FluidResizeTaskPositioner(
taskOrganizer,
transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
new file mode 100644
index 000000000000..8dc921c986ce
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.view.Choreographer
+import android.view.Surface
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.Transitions
+import java.util.concurrent.TimeUnit
+import java.util.function.Supplier
+
+/**
+ * A task positioner that also takes into account resizing a
+ * [com.android.wm.shell.windowdecor.ResizeVeil] and dragging move across multiple displays.
+ * - If the drag is resizing the task, we resize the veil instead.
+ * - If the drag is repositioning, we consider multi-display topology if needed, and update in the
+ * typical manner.
+ */
+class MultiDisplayVeiledResizeTaskPositioner(
+ private val taskOrganizer: ShellTaskOrganizer,
+ private val desktopWindowDecoration: DesktopModeWindowDecoration,
+ private val displayController: DisplayController,
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val transitions: Transitions,
+ private val interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread private val handler: Handler,
+) : TaskPositioner, Transitions.TransitionHandler {
+ private val dragEventListeners =
+ mutableListOf<DragPositioningCallbackUtility.DragEventListener>()
+ private val stableBounds = Rect()
+ private val taskBoundsAtDragStart = Rect()
+ private val repositionStartPoint = PointF()
+ private val repositionTaskBounds = Rect()
+ private val isResizing: Boolean
+ get() =
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_TOP) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_BOTTOM) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_LEFT) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_RIGHT) != 0
+
+ @DragPositioningCallback.CtrlType private var ctrlType = 0
+ private var isResizingOrAnimatingResize = false
+ @Surface.Rotation private var rotation = 0
+
+ constructor(
+ taskOrganizer: ShellTaskOrganizer,
+ windowDecoration: DesktopModeWindowDecoration,
+ displayController: DisplayController,
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener,
+ transitions: Transitions,
+ interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread handler: Handler,
+ ) : this(
+ taskOrganizer,
+ windowDecoration,
+ displayController,
+ dragEventListener,
+ Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() },
+ transitions,
+ interactionJankMonitor,
+ handler,
+ )
+
+ init {
+ dragEventListeners.add(dragEventListener)
+ }
+
+ override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
+ this.ctrlType = ctrlType
+ taskBoundsAtDragStart.set(
+ desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds
+ )
+ repositionStartPoint[x] = y
+ if (isResizing) {
+ // Capture CUJ for re-sizing window in DW mode.
+ interactionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ )
+ if (!desktopWindowDecoration.mHasGlobalFocus) {
+ val wct = WindowContainerTransaction()
+ wct.reorder(
+ desktopWindowDecoration.mTaskInfo.token,
+ /* onTop= */ true,
+ /* includingParents= */ true,
+ )
+ taskOrganizer.applyTransaction(wct)
+ }
+ }
+ for (dragEventListener in dragEventListeners) {
+ dragEventListener.onDragStart(desktopWindowDecoration.mTaskInfo.taskId)
+ }
+ repositionTaskBounds.set(taskBoundsAtDragStart)
+ val rotation =
+ desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.displayRotation
+ if (stableBounds.isEmpty || this.rotation != rotation) {
+ this.rotation = rotation
+ displayController
+ .getDisplayLayout(desktopWindowDecoration.mDisplay.displayId)!!
+ .getStableBounds(stableBounds)
+ }
+ return Rect(repositionTaskBounds)
+ }
+
+ override fun onDragPositioningMove(displayId: Int, x: Float, y: Float): Rect {
+ check(Looper.myLooper() == handler.looper) {
+ "This method must run on the shell main thread."
+ }
+ val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
+ if (
+ isResizing &&
+ DragPositioningCallbackUtility.changeBounds(
+ ctrlType,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ stableBounds,
+ delta,
+ displayController,
+ desktopWindowDecoration,
+ )
+ ) {
+ if (!isResizingOrAnimatingResize) {
+ for (dragEventListener in dragEventListeners) {
+ dragEventListener.onDragMove(desktopWindowDecoration.mTaskInfo.taskId)
+ }
+ desktopWindowDecoration.showResizeVeil(repositionTaskBounds)
+ isResizingOrAnimatingResize = true
+ } else {
+ desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
+ }
+ } else if (ctrlType == DragPositioningCallback.CTRL_TYPE_UNDEFINED) {
+ // Begin window drag CUJ instrumentation only when drag position moves.
+ interactionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ )
+ val t = transactionSupplier.get()
+ DragPositioningCallbackUtility.setPositionOnDrag(
+ desktopWindowDecoration,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ t,
+ x,
+ y,
+ )
+ t.setFrameTimeline(Choreographer.getInstance().vsyncId)
+ t.apply()
+ }
+ return Rect(repositionTaskBounds)
+ }
+
+ override fun onDragPositioningEnd(displayId: Int, x: Float, y: Float): Rect {
+ val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
+ if (isResizing) {
+ if (taskBoundsAtDragStart != repositionTaskBounds) {
+ DragPositioningCallbackUtility.changeBounds(
+ ctrlType,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ stableBounds,
+ delta,
+ displayController,
+ desktopWindowDecoration,
+ )
+ desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
+ val wct = WindowContainerTransaction()
+ wct.setBounds(desktopWindowDecoration.mTaskInfo.token, repositionTaskBounds)
+ transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, this)
+ } else {
+ // If bounds haven't changed, perform necessary veil reset here as startAnimation
+ // won't be called.
+ resetVeilIfVisible()
+ }
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ } else {
+ DragPositioningCallbackUtility.updateTaskBounds(
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ x,
+ y,
+ )
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ }
+
+ ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
+ taskBoundsAtDragStart.setEmpty()
+ repositionStartPoint[0f] = 0f
+ return Rect(repositionTaskBounds)
+ }
+
+ private fun resetVeilIfVisible() {
+ if (isResizingOrAnimatingResize) {
+ desktopWindowDecoration.hideResizeVeil()
+ isResizingOrAnimatingResize = false
+ }
+ }
+
+ private fun createLongTimeoutJankConfigBuilder(@Cuj.CujType cujType: Int) =
+ InteractionJankMonitor.Configuration.Builder.withSurface(
+ cujType,
+ desktopWindowDecoration.mContext,
+ desktopWindowDecoration.mTaskSurface,
+ handler,
+ )
+ .setTimeout(LONG_CUJ_TIMEOUT_MS)
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ for (change in info.changes) {
+ val sc = change.leash
+ val endBounds = change.endAbsBounds
+ val endPosition = change.endRelOffset
+ startTransaction
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+ finishTransaction
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+ }
+
+ startTransaction.apply()
+ resetVeilIfVisible()
+ ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
+ finishCallback.onTransitionFinished(null /* wct */)
+ isResizingOrAnimatingResize = false
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ return true
+ }
+
+ /**
+ * We should never reach this as this handler's transitions are only started from shell
+ * explicitly.
+ */
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? {
+ return null
+ }
+
+ override fun isResizingOrAnimating() = isResizingOrAnimatingResize
+
+ override fun addDragEventListener(
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener
+ ) {
+ dragEventListeners.add(dragEventListener)
+ }
+
+ override fun removeDragEventListener(
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener
+ ) {
+ dragEventListeners.remove(dragEventListener)
+ }
+
+ companion object {
+ // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
+ // timing out in the middle of a resize or drag action.
+ private val LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(/* duration= */ 10L)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index e011cc08903b..d2c79d76e6c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -53,7 +53,12 @@ import java.util.function.Supplier;
* {@link com.android.wm.shell.windowdecor.ResizeVeil}.
* If the drag is resizing the task, we resize the veil instead.
* If the drag is repositioning, we update in the typical manner.
+ * <p>
+ * @deprecated This class will be replaced by
+ * {@link com.android.wm.shell.windowdecor.MultiDisplayVeiledResizeTaskPositioner}.
+ * TODO(b/383632995): Remove this class after MultiDisplayVeiledResizeTaskPositioner is launched.
*/
+@Deprecated
public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
// Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
// timing out in the middle of a resize or drag action.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 95ed8b4d4511..f549b0563827 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -601,7 +601,32 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(4)
+ // Expect order to be from bottom: home, wallpaperIntent, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 2, task1)
+ wct.assertReorderAt(index = 3, task2)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index c42f6c35bcb0..aef44a40fa0f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -16,14 +16,10 @@
package com.android.wm.shell.pip2.phone;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
-
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import static org.mockito.kotlin.MatchersKt.eq;
@@ -124,12 +120,13 @@ public class PipSchedulerTest {
.setCallsite("PipSchedulerTest")
.build();
when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(testLeash);
+ // PiP is in a valid state by default.
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
}
@Test
public void scheduleExitPipViaExpand_nullTaskToken_noop() {
setNullPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleExitPipViaExpand();
@@ -137,14 +134,12 @@ public class PipSchedulerTest {
assertNotNull(mRunnableArgumentCaptor.getValue());
mRunnableArgumentCaptor.getValue().run();
- verify(mMockPipTransitionController, never())
- .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+ verify(mMockPipTransitionController, never()).startExpandTransition(any());
}
@Test
public void scheduleExitPipViaExpand_exitTransitionCalled() {
setMockPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleExitPipViaExpand();
@@ -152,23 +147,21 @@ public class PipSchedulerTest {
assertNotNull(mRunnableArgumentCaptor.getValue());
mRunnableArgumentCaptor.getValue().run();
- verify(mMockPipTransitionController, times(1))
- .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+ verify(mMockPipTransitionController, times(1)).startExpandTransition(any());
}
@Test
public void removePipAfterAnimation() {
setMockPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
- mPipScheduler.scheduleRemovePip();
+ mPipScheduler.scheduleRemovePip(true /* withFadeout */);
verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
assertNotNull(mRunnableArgumentCaptor.getValue());
mRunnableArgumentCaptor.getValue().run();
verify(mMockPipTransitionController, times(1))
- .startExitTransition(eq(TRANSIT_REMOVE_PIP), any(), isNull());
+ .startRemoveTransition(true /* withFadeout */);
}
@Test
@@ -192,7 +185,6 @@ public class PipSchedulerTest {
@Test
public void scheduleAnimateResizePip_boundsConfig_setsConfigAtEnd() {
setMockPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
@@ -233,7 +225,6 @@ public class PipSchedulerTest {
@Test
public void scheduleAnimateResizePip_resizeTransition() {
setMockPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
index 571ae93e1aec..fa9b5903bc3c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
@@ -110,4 +110,22 @@ public class PipTransitionStateTest extends ShellTestCase {
mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
}
+
+ @Test
+ public void testBoundsChangeState_notInPip() {
+ Bundle extra = new Bundle();
+ extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+ mPipTransitionState.setState(PipTransitionState.UNDEFINED);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+ Assert.assertEquals(PipTransitionState.UNDEFINED, mPipTransitionState.getState());
+
+ mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+ Assert.assertEquals(PipTransitionState.ENTERING_PIP, mPipTransitionState.getState());
+
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+ Assert.assertEquals(PipTransitionState.EXITING_PIP, mPipTransitionState.getState());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 2d0ea5fdc884..7a88ace3f85f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
@@ -32,6 +33,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.atLeastOnce;
@@ -50,9 +53,11 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.view.SurfaceControl;
import android.window.RemoteTransition;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
@@ -84,6 +89,7 @@ import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -425,6 +431,30 @@ public class StageCoordinatorTests extends ShellTestCase {
.startFullscreenTransition(any(), any());
}
+
+ @Test
+ public void startTask_ensureWindowingModeCleared() {
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ ArgumentCaptor<WindowContainerTransaction> wctCaptor =
+ ArgumentCaptor.forClass(WindowContainerTransaction.class);
+ int taskId = 18;
+ IBinder binder = mock(IBinder.class);
+ ActivityManager.RunningTaskInfo rti = mock(ActivityManager.RunningTaskInfo.class);
+ WindowContainerToken mockToken = mock(WindowContainerToken.class);
+ when(mockToken.asBinder()).thenReturn(binder);
+ when(rti.getToken()).thenReturn(mockToken);
+ when(mTaskOrganizer.getRunningTaskInfo(taskId)).thenReturn(rti);
+ mStageCoordinator.startTask(taskId, SPLIT_POSITION_TOP_OR_LEFT, null /*options*/,
+ null, SPLIT_INDEX_UNDEFINED);
+ verify(splitScreenTransitions).startEnterTransition(anyInt(),
+ wctCaptor.capture(), any(), any(), anyInt(), anyBoolean());
+
+ int windowingMode = wctCaptor.getValue().getChanges().get(binder).getWindowingMode();
+ assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED);
+ }
+
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
new file mode 100644
index 000000000000..f179cac32244
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import java.util.function.Supplier
+import junit.framework.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [MultiDisplayVeiledResizeTaskPositioner].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:MultiDisplayVeiledResizeTaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
+
+ @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
+ @Mock
+ private lateinit var mockDragEventListener: DragPositioningCallbackUtility.DragEventListener
+
+ @Mock private lateinit var taskToken: WindowContainerToken
+ @Mock private lateinit var taskBinder: IBinder
+
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock private lateinit var mockDisplay: Display
+ @Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
+ @Mock private lateinit var mockTransaction: SurfaceControl.Transaction
+ @Mock private lateinit var mockTransitionBinder: IBinder
+ @Mock private lateinit var mockTransitionInfo: TransitionInfo
+ @Mock private lateinit var mockFinishCallback: TransitionFinishCallback
+ @Mock private lateinit var mockTransitions: Transitions
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var mockResources: Resources
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ mockDesktopWindowDecoration.mDecorWindowContext = mockContext
+ whenever(mockContext.getResources()).thenReturn(mockResources)
+ whenever(taskToken.asBinder()).thenReturn(taskBinder)
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ if (
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
+ }
+ `when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
+ mockDesktopWindowDecoration.mTaskInfo =
+ ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ minWidth = MIN_WIDTH
+ minHeight = MIN_HEIGHT
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
+ isResizeable = true
+ }
+ `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+ taskPositioner =
+ MultiDisplayVeiledResizeTaskPositioner(
+ mockShellTaskOrganizer,
+ mockDesktopWindowDecoration,
+ mockDisplayController,
+ mockDragEventListener,
+ mockTransactionFactory,
+ mockTransitions,
+ mockInteractionJankMonitor,
+ mainHandler,
+ )
+ }
+
+ @Test
+ fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ verify(mockTransitions, never())
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == STARTING_BOUNDS
+ }
+ },
+ eq(taskPositioner),
+ )
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ }
+
+ @Test
+ fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 60,
+ STARTING_BOUNDS.top.toFloat() + 100,
+ )
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.left += 60
+ rectAfterMove.right += 60
+ rectAfterMove.top += 100
+ rectAfterMove.bottom += 100
+ verify(mockTransaction)
+ .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))
+
+ val endBounds =
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 70,
+ STARTING_BOUNDS.top.toFloat() + 20,
+ )
+ val rectAfterEnd = Rect(STARTING_BOUNDS)
+ rectAfterEnd.left += 70
+ rectAfterEnd.right += 70
+ rectAfterEnd.top += 20
+ rectAfterEnd.bottom += 20
+
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ Assert.assertEquals(rectAfterEnd, endBounds)
+ }
+
+ @Test
+ fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10,
+ )
+
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.right += 10
+ rectAfterMove.top += 10
+ verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterMove
+ }
+ }
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat() + 20,
+ STARTING_BOUNDS.top.toFloat() + 20,
+ )
+ val rectAfterEnd = Rect(rectAfterMove)
+ rectAfterEnd.right += 10
+ rectAfterEnd.top += 10
+ verify(mockDesktopWindowDecoration).updateResizeVeil(any())
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }
+ },
+ eq(taskPositioner),
+ )
+ }
+
+ @Test
+ fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ DISPLAY_ID,
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10,
+ )
+
+ verify(mockTransitions, never())
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == STARTING_BOUNDS
+ }
+ },
+ eq(taskPositioner),
+ )
+
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0)
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ val newX = STARTING_BOUNDS.left.toFloat() + 5
+ val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
+ taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY)
+
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
+
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0)
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = false
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is reordered to top
+ verify(mockShellTaskOrganizer)
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = true
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is not reordered to top
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = false
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is not reordered to top since task is already brought to top before dragging
+ // begins
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread {
+ // Test landscape stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ },
+ eq(taskPositioner),
+ )
+ // Drag back to starting bounds.
+ performDrag(
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockDesktopWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+ rectAfterDrag.right = STABLE_BOUNDS_PORTRAIT.right
+ rectAfterDrag.bottom = STARTING_BOUNDS.bottom + 2000
+
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ },
+ eq(taskPositioner),
+ )
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeSet() = runOnUiThread {
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20,
+ )
+
+ // isResizingOrAnimating should be set to true after move during a resize
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+ verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID))
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // isResizingOrAnimating should be not be set till false until after transition animation
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread {
+ performDrag(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20,
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ )
+
+ taskPositioner.startAnimation(
+ mockTransitionBinder,
+ mockTransitionInfo,
+ mockTransaction,
+ mockTransaction,
+ mockFinishCallback,
+ )
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testStartAnimation_useEndRelOffset() = runOnUiThread {
+ val changeMock = mock(TransitionInfo.Change::class.java)
+ val startTransaction = mock(Transaction::class.java)
+ val finishTransaction = mock(Transaction::class.java)
+ val point = Point(10, 20)
+ val bounds = Rect(1, 2, 3, 4)
+ `when`(changeMock.leash).thenReturn(mock(SurfaceControl::class.java))
+ `when`(changeMock.endRelOffset).thenReturn(point)
+ `when`(changeMock.endAbsBounds).thenReturn(bounds)
+ `when`(mockTransitionInfo.changes).thenReturn(listOf(changeMock))
+ `when`(startTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height())))
+ .thenReturn(startTransaction)
+ `when`(finishTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height())))
+ .thenReturn(finishTransaction)
+
+ taskPositioner.startAnimation(
+ mockTransitionBinder,
+ mockTransitionInfo,
+ startTransaction,
+ finishTransaction,
+ mockFinishCallback,
+ )
+
+ verify(startTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat()))
+ verify(finishTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat()))
+ verify(changeMock).endRelOffset
+ }
+
+ private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) {
+ taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY)
+ taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY)
+
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY)
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private const val MIN_WIDTH = 10
+ private const val MIN_HEIGHT = 10
+ private const val DENSITY_DPI = 20
+ private const val DEFAULT_MIN = 40
+ private const val DISPLAY_ID = 1
+ private const val NAVBAR_HEIGHT = 50
+ private const val CAPTION_HEIGHT = 50
+ private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+ private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
+ private val STABLE_BOUNDS_LANDSCAPE =
+ Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ )
+ private val STABLE_BOUNDS_PORTRAIT =
+ Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT,
+ )
+ private val VALID_DRAG_AREA =
+ Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100,
+ )
+ }
+}
diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java
index cdde7c66e268..9746dab4151c 100644
--- a/location/java/android/location/Geocoder.java
+++ b/location/java/android/location/Geocoder.java
@@ -83,8 +83,11 @@ public final class Geocoder {
* succeed.
*/
public static boolean isPresent() {
- ILocationManager lm = Objects.requireNonNull(ILocationManager.Stub.asInterface(
- ServiceManager.getService(Context.LOCATION_SERVICE)));
+ ILocationManager lm = ILocationManager.Stub.asInterface(
+ ServiceManager.getService(Context.LOCATION_SERVICE));
+ if (lm == null) {
+ return false;
+ }
try {
return lm.isGeocodeAvailable();
} catch (RemoteException e) {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 2fe069af638a..bf330dab266c 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -701,6 +701,9 @@ void FilterClientCallbackImpl::getMediaEvent(const jobjectArray& arr, const int
// Protect mFilterClient from being set to null.
android::Mutex::Autolock autoLock(mLock);
+ if (mFilterClient == nullptr) {
+ return;
+ }
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
(dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -868,10 +871,18 @@ void FilterClientCallbackImpl::getRestartEvent(const jobjectArray& arr, const in
void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
ALOGV("FilterClientCallbackImpl::onFilterEvent");
JNIEnv *env = AndroidRuntime::getJNIEnv();
+
ScopedLocalRef<jobjectArray> array(env);
if (!events.empty()) {
array.reset(env->NewObjectArray(events.size(), mEventClass, nullptr));
+ if (env->IsSameObject(array.get(), nullptr)) {
+ // It can happen when FilterClientCallbackImpl release the resource
+ // in another thread.
+ ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+ "Unable to create object array of filter events. Ignoring callback.");
+ return;
+ }
}
for (int i = 0, arraySize = 0; i < events.size(); i++) {
@@ -1070,14 +1081,15 @@ FilterClientCallbackImpl::FilterClientCallbackImpl() {
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- {
- android::Mutex::Autolock autoLock(mLock);
- if (mFilterObj != nullptr) {
- env->DeleteWeakGlobalRef(mFilterObj);
- mFilterObj = nullptr;
- }
- mFilterClient = nullptr;
+
+ android::Mutex::Autolock autoLock(mLock);
+
+ if (mFilterObj != nullptr) {
+ env->DeleteWeakGlobalRef(mFilterObj);
+ mFilterObj = nullptr;
}
+ mFilterClient = nullptr;
+
env->DeleteGlobalRef(mEventClass);
env->DeleteGlobalRef(mSectionEventClass);
env->DeleteGlobalRef(mMediaEventClass);
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index e4f88a65ed1a..e63c59d53f2a 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -9,7 +9,7 @@ package {
android_test {
name: "mediaroutertest",
- team: "trendy_team_android_media_solutions",
+ team: "trendy_team_android_media_better_together",
srcs: ["**/*.java"],
diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING
index be84574e6a2c..70560a84b88e 100644
--- a/native/android/TEST_MAPPING
+++ b/native/android/TEST_MAPPING
@@ -16,9 +16,7 @@
{
"name": "CtsOsTestCases_cts_performancehintmanagertest",
"file_patterns": ["performance_hint.cpp"]
- }
- ],
- "postsubmit": [
+ },
{
"name": "CtsThermalTestCases",
"file_patterns": ["thermal.cpp"]
diff --git a/nfc-non-updatable/flags/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig
index 6b14a1ed3990..54ded0cddffa 100644
--- a/nfc-non-updatable/flags/flags.aconfig
+++ b/nfc-non-updatable/flags/flags.aconfig
@@ -197,3 +197,11 @@ flag {
description: "Expose constructor for ApduServiceInfo"
bug: "380892385"
}
+
+flag {
+ name: "nfc_hce_latency_events"
+ is_exported: true
+ namespace: "wallet_integration"
+ description: "Enables tracking latency for HCE"
+ bug: "379849603"
+}
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index 754abb2f76be..96e5892f4d1d 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -33,7 +33,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
- android:icon="@drawable/android15_patch_adaptive"
+ android:icon="@drawable/android16_patch_adaptive"
android:label="@string/app_name">
<!-- Android V easter egg: Daydream version of Landroid
@@ -41,7 +41,7 @@
<service
android:name=".landroid.DreamUniverse"
android:exported="true"
- android:icon="@drawable/android15_patch_adaptive"
+ android:icon="@drawable/android16_patch_adaptive"
android:label="@string/v_egg_name"
android:description="@string/dream_description"
android:enabled="false"
@@ -62,7 +62,7 @@
android:name=".landroid.MainActivity"
android:exported="true"
android:label="@string/u_egg_name"
- android:icon="@drawable/android15_patch_adaptive"
+ android:icon="@drawable/android16_patch_adaptive"
android:configChanges="orientation|screenLayout|screenSize|density"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<intent-filter>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml
new file mode 100644
index 000000000000..277df47438e3
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/android16_patch_adaptive_background"/>
+ <foreground android:drawable="@drawable/android16_patch_adaptive_foreground"/>
+ <monochrome android:drawable="@drawable/android16_patch_monochrome"/>
+</adaptive-icon>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml
new file mode 100644
index 000000000000..17c2b927f4fd
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml
@@ -0,0 +1,245 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <group>
+ <clip-path
+ android:pathData="M0,0h108v108h-108z"/>
+ <path
+ android:pathData="M73,54L54,35L35,54L54,73L73,54Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M44.5,44.5L54,44.5L44.5,54L44.5,44.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M63.5,63.5L54,63.5L63.5,54L63.5,63.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,54L54,44.5L63.5,54L54,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,44.5L54,35L63.5,44.5L54,44.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,63.5L54,73L44.5,63.5L54,63.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M63.5,54L63.5,44.5L73,54L63.5,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M44.5,54L44.5,63.5L35,54L44.5,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,54L54,63.5L44.5,54L54,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M82.5,25.5L82.5,35L73,25.5L82.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,44.5L63.5,35L73,44.5L63.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,35L82.5,35L73,44.5L73,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,35L92,35L82.5,44.5L82.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,35L54,35L63.5,25.5L63.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,44.5L82.5,44.5L73,54L73,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,25.5L63.5,25.5L73,16L73,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,35L63.5,35L73,25.5L73,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,63.5L82.5,73L73,63.5L82.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,82.5L63.5,73L73,82.5L63.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,73L82.5,73L73,82.5L73,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,73L92,73L82.5,82.5L82.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,73L54,73L63.5,63.5L63.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,82.5L82.5,82.5L73,92L73,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,63.5L63.5,63.5L73,54L73,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,73L63.5,73L73,63.5L73,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,63.5L44.5,73L35,63.5L44.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,82.5L25.5,73L35,82.5L25.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,73L44.5,73L35,82.5L35,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,73L54,73L44.5,82.5L44.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,73L16,73L25.5,63.5L25.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,82.5L44.5,82.5L35,92L35,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,63.5L25.5,63.5L35,54L35,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,73L25.5,73L35,63.5L35,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,25.5L44.5,35L35,25.5L44.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,44.5L25.5,35L35,44.5L25.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,35L44.5,35L35,44.5L35,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,35L54,35L44.5,44.5L44.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,35L16,35L25.5,25.5L25.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,44.5L44.5,44.5L35,54L35,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,25.5L25.5,25.5L35,16L35,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,35L25.5,35L35,25.5L35,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,25.5L54,25.5L63.5,16L63.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,6.5L54,6.5L44.5,16L44.5,6.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,16L54,25.5L44.5,16L54,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,25.5L54,35L44.5,25.5L54,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,6.5L54,-3L63.5,6.5L54,6.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,16L44.5,25.5L35,16L44.5,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,16L63.5,6.5L73,16L63.5,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,16L54,6.5L63.5,16L54,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M101.5,63.5L92,63.5L101.5,54L101.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,44.5L92,44.5L82.5,54L82.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,54L92,63.5L82.5,54L92,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,63.5L92,73L82.5,63.5L92,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,44.5L92,35L101.5,44.5L92,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,54L82.5,63.5L73,54L82.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M101.5,54L101.5,44.5L111,54L101.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,54L92,44.5L101.5,54L92,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,101.5L54,101.5L63.5,92L63.5,101.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,82.5L54,82.5L44.5,92L44.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,92L54,101.5L44.5,92L54,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,101.5L54,111L44.5,101.5L54,101.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,82.5L54,73L63.5,82.5L54,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,92L44.5,101.5L35,92L44.5,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,92L63.5,82.5L73,92L63.5,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,92L54,82.5L63.5,92L54,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,63.5L16,63.5L25.5,54L25.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M6.5,44.5L16,44.5L6.5,54L6.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,54L16,63.5L6.5,54L16,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,63.5L16,73L6.5,63.5L16,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,44.5L16,35L25.5,44.5L16,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M6.5,54L6.5,63.5L-3,54L6.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,54L25.5,44.5L35,54L25.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,54L16,44.5L25.5,54L16,54Z"
+ android:fillColor="#16161D"/>
+ </group>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml
new file mode 100644
index 000000000000..4c2932399c1a
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+ android:fillColor="#C6FF00"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.973 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.557C62.817,58.796 63.205,58.796 63.444,58.557L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml b/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml
new file mode 100644
index 000000000000..608d5ea6ee48
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml
@@ -0,0 +1,113 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:strokeWidth="1"
+ android:pathData="M54.707,35.707L72.293,53.293A1,1 102.155,0 1,72.293 54.707L54.707,72.293A1,1 0,0 1,53.293 72.293L35.707,54.707A1,1 0,0 1,35.707 53.293L53.293,35.707A1,1 0,0 1,54.707 35.707z"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M55.237,35.177L72.823,52.763A1.75,1.75 67.835,0 1,72.823 55.237L55.237,72.823A1.75,1.75 77.684,0 1,52.763 72.823L35.177,55.237A1.75,1.75 0,0 1,35.177 52.763L52.763,35.177A1.75,1.75 0,0 1,55.237 35.177z"
+ android:strokeWidth="1.5"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M44.5,44.5h19v19h-19z"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M54,44.5l9.5,9.5l-9.5,9.5l-9.5,-9.5z"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M54,35V73"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M73,54L35,54"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M33.576,31.135l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M31.146,65.966l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M26.718,56l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M31.146,48l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M41.925,34.374l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M63.146,71l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M48.567,74.553l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M51.146,26l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M72.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M76.531,36.417l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M58.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M68.419,36.978l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M74.252,64.034l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M71.437,76.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M42.984,69.38l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M82.437,51.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.972 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.556C62.817,58.796 63.205,58.796 63.444,58.556L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
index 8c87c5d4af7b..d56e8b9e8d0e 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
@@ -59,7 +59,7 @@ class DreamUniverse : DreamService() {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed())
+ val universe = Universe(namer = Namer(resources), randomSeed = randomSeed())
isInteractive = false
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
index 16ec1a933d92..4f77b00b7570 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
@@ -26,7 +26,6 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.withInfiniteAnimationFrameNanos
-import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.forEachGesture
@@ -45,6 +44,7 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -64,7 +64,6 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -75,6 +74,9 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import java.lang.Float.max
import java.lang.Float.min
import java.util.Calendar
@@ -83,9 +85,6 @@ import kotlin.math.absoluteValue
import kotlin.math.floor
import kotlin.math.sqrt
import kotlin.random.Random
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
enum class RandomSeedType {
Fixed,
@@ -139,7 +138,6 @@ fun getDessertCode(): String =
else -> Build.VERSION.RELEASE_OR_CODENAME.replace(Regex("[a-z]*"), "")
}
-
val DEBUG_TEXT = mutableStateOf("Hello Universe")
const val SHOW_DEBUG_TEXT = false
@@ -158,7 +156,7 @@ fun DebugText(text: MutableState<String>) {
}
@Composable
-fun Telemetry(universe: VisibleUniverse) {
+fun Telemetry(universe: Universe) {
var topVisible by remember { mutableStateOf(false) }
var bottomVisible by remember { mutableStateOf(false) }
@@ -180,10 +178,15 @@ fun Telemetry(universe: VisibleUniverse) {
topVisible = true
}
- universe.triggerDraw.value // recompose on every frame
-
val explored = universe.planets.filter { it.explored }
+ // TODO: Narrow the scope of invalidation here to the specific data needed;
+ // the behavior below mimics the previous implementation of a snapshot ticker value
+ val recomposeScope = currentRecomposeScope
+ Telescope(universe) {
+ recomposeScope.invalidate()
+ }
+
BoxWithConstraints(
modifier =
Modifier.fillMaxSize().padding(6.dp).windowInsetsPadding(WindowInsets.safeContent),
@@ -299,7 +302,7 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
- val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed())
+ val universe = Universe(namer = Namer(resources), randomSeed = randomSeed())
if (TEST_UNIVERSE) {
universe.initTest()
@@ -373,7 +376,7 @@ class MainActivity : ComponentActivity() {
@Preview(name = "tablet", device = Devices.TABLET)
@Composable
fun MainActivityPreview() {
- val universe = VisibleUniverse(namer = Namer(Resources.getSystem()), randomSeed = randomSeed())
+ val universe = Universe(namer = Namer(Resources.getSystem()), randomSeed = randomSeed())
universe.initTest()
@@ -458,12 +461,12 @@ fun FlightStick(
@Composable
fun Spaaaace(
modifier: Modifier,
- u: VisibleUniverse,
+ u: Universe,
foldState: MutableState<FoldingFeature?> = mutableStateOf(null)
) {
LaunchedEffect(u) {
while (true) withInfiniteAnimationFrameNanos { frameTimeNanos ->
- u.simulateAndDrawFrame(frameTimeNanos)
+ u.step(frameTimeNanos)
}
}
@@ -492,7 +495,7 @@ fun Spaaaace(
val centerFracY: Float by
animateFloatAsState(if (halfFolded && horizontalFold) 0.25f else 0.5f, label = "centerY")
- Canvas(modifier = canvasModifier) {
+ UniverseCanvas(u, canvasModifier) { u ->
drawRect(Colors.Eigengrau, Offset.Zero, size)
val closest = u.closestPlanet()
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
index d14234ec66d9..b8c68818888a 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
@@ -17,6 +17,8 @@
package com.android.egg.landroid
import android.util.ArraySet
+import androidx.compose.ui.util.fastForEach
+import kotlinx.coroutines.DisposableHandle
import kotlin.random.Random
// artificially speed up or slow down the simulation
@@ -127,6 +129,7 @@ open class Simulator(val randomSeed: Long) {
val rng = Random(randomSeed)
val entities = ArraySet<Entity>(1000)
val constraints = ArraySet<Constraint>(100)
+ private val simStepListeners = mutableListOf<() -> Unit>()
fun add(e: Entity) = entities.add(e)
fun remove(e: Entity) = entities.remove(e)
@@ -169,5 +172,26 @@ open class Simulator(val randomSeed: Long) {
// 3. compute new velocities from updated positions and saved positions
postUpdateAll(dt, localEntities)
+
+ // 4. notify listeners that step is complete
+ simStepListeners.fastForEach { it.invoke() }
+ }
+
+ /**
+ * Register [listener] to be invoked every time the [Simulator] completes one [step].
+ * Use this to enqueue drawing.
+ *
+ * Instead of the usual register()/unregister() pattern, we're going to borrow
+ * [kotlinx.coroutines.DisposableHandle] here. Call [DisposableHandle.dispose] on the return
+ * value to unregister.
+ */
+ fun addSimulationStepListener(listener: () -> Unit): DisposableHandle {
+ // add to listener list
+ simStepListeners += listener
+
+ return DisposableHandle {
+ // on dispose, remove from listener list
+ simStepListeners -= listener
+ }
}
}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
index ed3ebc7bf9a5..c476d5cf0b8b 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
@@ -16,19 +16,31 @@
package com.android.egg.landroid
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.PointMode
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.rotateRad
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.util.lerp
import androidx.core.math.MathUtils.clamp
import com.android.egg.flags.Flags.flagFlag
+import kotlinx.coroutines.DisposableHandle
import java.lang.Float.max
import kotlin.math.exp
import kotlin.math.sqrt
@@ -55,22 +67,108 @@ fun DrawScope.zoom(zoom: Float, block: ZoomedDrawScope.() -> Unit) {
ds.scale(zoom) { block(ds) }
}
-class VisibleUniverse(namer: Namer, randomSeed: Long) : Universe(namer, randomSeed) {
- // Magic variable. Every time we update it, Compose will notice and redraw the universe.
- val triggerDraw = mutableStateOf(0L)
+/**
+ * A device for observing changes to a [Simulator] such as a [Universe].
+ * [observer] will be invoked each time a [Simulator.step] has completed.
+ */
+@Composable
+fun <S : Simulator> Telescope(
+ subject: S,
+ observer: (S) -> Unit
+) {
+ remember(subject) {
+ object : RememberObserver {
+ lateinit var registration: DisposableHandle
+ var currentObserver by mutableStateOf(observer)
+
+ override fun onRemembered() {
+ registration = subject.addSimulationStepListener { currentObserver(subject) }
+ }
+
+ override fun onForgotten() {
+ registration.dispose()
+ }
+
+ override fun onAbandoned() {}
+ }
+ }.currentObserver = observer
+}
+
+fun Modifier.drawUniverse(
+ universe: Universe,
+ draw: DrawScope.(Universe) -> Unit
+): Modifier = this then UniverseElement(universe, draw)
+
+@Composable
+fun UniverseCanvas(
+ universe: Universe,
+ modifier: Modifier = Modifier,
+ draw: DrawScope.(Universe) -> Unit
+) {
+ Spacer(modifier.drawUniverse(universe, draw))
+}
+
+private class UniverseElement(
+ val universe: Universe,
+ val draw: DrawScope.(Universe) -> Unit
+) : ModifierNodeElement<UniverseModifierNode>() {
+ override fun create(): UniverseModifierNode = UniverseModifierNode(universe, draw)
+
+ // Called when a modifier is applied to a Layout whose inputs have changed
+ override fun update(node: UniverseModifierNode) {
+ node.universe = universe
+ node.draw = draw
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
- fun simulateAndDrawFrame(nanos: Long) {
- // By writing this value, Compose will look for functions that read it (like drawZoomed).
- triggerDraw.value = nanos
+ other as UniverseElement
- step(nanos)
+ if (universe != other.universe) return false
+ if (draw != other.draw) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = universe.hashCode()
+ result = 31 * result + draw.hashCode()
+ return result
}
}
-fun ZoomedDrawScope.drawUniverse(universe: VisibleUniverse) {
- with(universe) {
- triggerDraw.value // Please recompose when this value changes.
+private class UniverseModifierNode(
+ universe: Universe,
+ draw: DrawScope.(Universe) -> Unit,
+) : Modifier.Node(), DrawModifierNode {
+ private val universeListener: () -> Unit = { invalidateDraw() }
+ private var removeUniverseListener: DisposableHandle? =
+ universe.addSimulationStepListener(universeListener)
+
+ var universe: Universe = universe
+ set(value) {
+ if (field === value) return
+ removeUniverseListener?.dispose()
+ field = value
+ removeUniverseListener = value.addSimulationStepListener(universeListener)
+ }
+
+ var draw: ContentDrawScope.(Universe) -> Unit = draw
+ set(value) {
+ if (field === value) return
+ field = value
+ invalidateDraw()
+ }
+ override fun ContentDrawScope.draw() {
+ draw(universe)
+ }
+}
+
+fun ZoomedDrawScope.drawUniverse(universe: Universe) {
+ with(universe) {
constraints.forEach {
when (it) {
is Landing -> drawLanding(it)
diff --git a/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml b/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml
new file mode 100644
index 000000000000..1d57c1617495
--- /dev/null
+++ b/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSecondaryContainer" />
+ <corners
+ android:radius="@dimen/settingslib_expressive_radius_extralarge3" />
+ </shape>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
index 9018baca79e7..4ce106e56822 100644
--- a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
+++ b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
@@ -14,9 +14,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.google.android.material.card.MaterialCardView
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/SettingsLibCardStyle">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
<LinearLayout
android:id="@+id/card_container"
@@ -24,10 +28,10 @@
android:layout_height="wrap_content"
android:baselineAligned="false"
android:minHeight="@dimen/settingslib_expressive_space_large3"
- android:paddingStart="@dimen/settingslib_expressive_space_small1"
- android:paddingEnd="@dimen/settingslib_expressive_space_small1"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_medium1"
android:orientation="horizontal"
- android:gravity="center_vertical">
+ android:gravity="center_vertical"
+ android:background="@drawable/settingslib_card_preference_background">
<LinearLayout
android:id="@+id/icon_frame"
@@ -35,15 +39,13 @@
android:layout_height="wrap_content"
android:minWidth="@dimen/settingslib_expressive_space_medium3"
android:minHeight="@dimen/settingslib_expressive_space_medium3"
- android:gravity="center"
- android:orientation="horizontal">
-
+ android:gravity="center">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/settingslib_expressive_space_medium3"
android:layout_height="@dimen/settingslib_expressive_space_medium3"
- android:scaleType="centerInside"/>
-
+ android:scaleType="centerInside"
+ android:importantForAccessibility="no"/>
</LinearLayout>
<LinearLayout
@@ -54,19 +56,16 @@
android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
android:paddingVertical="@dimen/settingslib_expressive_space_small2"
android:orientation="vertical">
-
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib"/>
-
+ android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib"/>
-
+ android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib" />
</LinearLayout>
<ImageView
@@ -75,9 +74,9 @@
android:layout_height="@dimen/settingslib_expressive_space_medium4"
android:padding="@dimen/settingslib_expressive_space_extrasmall4"
android:layout_gravity="center"
+ android:contentDescription="@string/settingslib_dismiss_button_content_description"
android:src="@drawable/settingslib_expressive_icon_close"
- android:background="?android:attr/selectableItemBackground" />
+ android:tint="@color/settingslib_materialColorOnSecondary" />
</LinearLayout>
-
-</com.google.android.material.card.MaterialCardView> \ No newline at end of file
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
index 287b13fa0d50..e7d4a0013896 100644
--- a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
+++ b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
@@ -18,11 +18,11 @@
<resources>
<style name="TextAppearance.CardTitle.SettingsLib"
parent="@style/TextAppearance.SettingsLib.TitleMedium.Emphasized">
- <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
</style>
<style name="TextAppearance.CardSummary.SettingsLib"
parent="@style/TextAppearance.SettingsLib.LabelMedium">
- <item name="android:textColor">@color/settingslib_materialColorOnSecondary</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
index 14e3b87cf325..38b641336547 100644
--- a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
+++ b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
@@ -114,15 +114,7 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
private fun generateCode(outputPkg: String, outputClass: String, outputFun: String) {
// sort by screen keys to make the output deterministic and naturally fit to FixedArrayMap
screens.sort()
- val javaFileObject =
- try {
- processingEnv.filer.createSourceFile("$outputPkg.$outputClass")
- } catch (e: Exception) {
- // quick fix: gradle runs this processor twice unexpectedly
- warn("cannot createSourceFile: $e")
- return
- }
- javaFileObject.openWriter().use {
+ processingEnv.filer.createSourceFile("$outputPkg.$outputClass").openWriter().use {
it.write("package $outputPkg;\n\n")
it.write("import $PACKAGE.FixedArrayMap;\n")
it.write("import $PACKAGE.FixedArrayMap.OrderedInitializer;\n")
diff --git a/packages/SettingsLib/SettingsTheme/res/values/strings.xml b/packages/SettingsLib/SettingsTheme/res/values/strings.xml
index c36dcb88b9fe..f3f077edc91d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/strings.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/strings.xml
@@ -21,4 +21,6 @@
<string name="settingslib_expressive_text_expand">Expand</string>
<!-- text of button to indicate user the textView is collapsable [CHAR LIMIT=NONE] -->
<string name="settingslib_expressive_text_collapse">Collapse</string>
+ <!-- Content description of the dismiss button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="settingslib_dismiss_button_content_description">Dismiss</string>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index bbe08f254283..d94450b1cabd 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -219,3 +219,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "adopt_primary_group_management_api"
+ namespace: "cross_device_experiences"
+ description: "Adopt Bluetooth LE broadcast primary group management APIs"
+ bug: "381946931"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index fa43915deb6a..5b25d0dc04e1 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -35,7 +35,6 @@ import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
-import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
@@ -48,7 +47,6 @@ import javax.tools.Diagnostic.Kind;
* Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources}
* subclasses.
*/
-@SupportedSourceVersion(SourceVersion.RELEASE_17)
@SupportedOptions(IndexableProcessor.PACKAGE_KEY)
@SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
public class IndexableProcessor extends AbstractProcessor {
@@ -69,6 +67,11 @@ public class IndexableProcessor extends AbstractProcessor {
private boolean mRanOnce;
@Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnvironment) {
if (mRanOnce) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 4b7cb36f2753..bf86911ee683 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -134,6 +134,8 @@ public class CsipDeviceManager {
// Do nothing if GroupId has been assigned
if (!isValidGroupId(cachedDevice.getGroupId())) {
final int newGroupId = getBaseGroupId(cachedDevice.getDevice());
+ log("updateCsipDevices: propose new group id " + newGroupId + " for device "
+ + cachedDevice.getDevice());
// Do nothing if there is no GroupId on Bluetooth device
if (isValidGroupId(newGroupId)) {
cachedDevice.setGroupId(newGroupId);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 7c24df9e9019..ff5e9e657213 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -358,6 +358,9 @@ public class LocalBluetoothProfileManager {
&& mProfile instanceof CsipSetCoordinatorProfile;
if (isAshaProfile && (newState == BluetoothProfile.STATE_CONNECTED)) {
+ if (DEBUG) {
+ Log.d(TAG, "onReceive, hearing aid profile connected, check hisyncid");
+ }
// Check if the HiSyncID has being initialized
if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice());
@@ -375,7 +378,9 @@ public class LocalBluetoothProfileManager {
}
if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) {
-
+ if (DEBUG) {
+ Log.d(TAG, "onReceive, hap/lea profile connected, check hearing aid info");
+ }
// Checks if both profiles are connected to the device. Hearing aid info need
// to be retrieved from these profiles separately.
if (cachedDevice.isConnectedLeAudioHearingAidDevice()) {
@@ -389,10 +394,16 @@ public class LocalBluetoothProfileManager {
}
if (isCsipProfile && (newState == BluetoothProfile.STATE_CONNECTED)) {
+ if (DEBUG) {
+ Log.d(TAG, "onReceive, csip profile connected, check group id");
+ }
// Check if the GroupID has being initialized
if (cachedDevice.getGroupId() == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
final Map<Integer, ParcelUuid> groupIdMap = getCsipSetCoordinatorProfile()
.getGroupUuidMapByDevice(cachedDevice.getDevice());
+ if (DEBUG) {
+ Log.d(TAG, "csip group uuid map = " + groupIdMap);
+ }
if (groupIdMap != null) {
for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
if (entry.getValue().equals(BluetoothUuid.CAP)) {
@@ -431,6 +442,9 @@ public class LocalBluetoothProfileManager {
mProfile.getProfileId());
}
if (needDispatchProfileConnectionState) {
+ if (DEBUG) {
+ Log.d(TAG, "needDispatchProfileConnectionState");
+ }
cachedDevice.refresh();
mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState,
mProfile.getProfileId());
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 84afb9f7a7e2..a1cf409733fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -37,10 +37,10 @@ public abstract class AbstractPreferenceController {
private static final String TAG = "AbstractPrefController";
- protected final Context mContext;
+ protected final @NonNull Context mContext;
private final DevicePolicyManager mDevicePolicyManager;
- public AbstractPreferenceController(Context context) {
+ public AbstractPreferenceController(@NonNull Context context) {
mContext = context;
mDevicePolicyManager =
(DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 9aaefe47fda2..58c7907f77de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -37,8 +37,9 @@ class FakeZenModeRepository : ZenModeRepository {
override val globalZenMode: StateFlow<Int>
get() = mutableZenMode.asStateFlow()
- private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
+ private val mutableModesFlow: MutableStateFlow<List<ZenMode>> by lazy {
MutableStateFlow(listOf(TestModeBuilder.MANUAL_DND))
+ }
override val modes: Flow<List<ZenMode>>
get() = mutableModesFlow.asStateFlow()
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index cb656bdd5d54..9b75a47d92ac 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1869,7 +1869,7 @@ public class SettingsProvider extends ContentProvider {
}
case MUTATION_OPERATION_RESET -> {
return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE,
- UserHandle.USER_SYSTEM, callingPackage, mode, tag);
+ owningUserId, callingPackage, mode, tag);
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index bf3afeda448e..0f6311552de9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -408,77 +408,8 @@ public class SettingsState {
Slog.w(LOG_TAG, "Bulk sync request to acongid failed.");
}
}
-
- if (Flags.disableBulkCompare()) {
- return;
- }
-
- // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
- if (requests == null) {
- Map<String, AconfigdFlagInfo> aconfigdFlagMap =
- AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket);
- compareFlagValueInNewStorage(
- mAconfigDefaultFlags,
- aconfigdFlagMap);
- }
- }
- }
- }
-
- // TODO(b/312444587): remove the comparison logic after Test Mission 2.
- public int compareFlagValueInNewStorage(
- Map<String, AconfigdFlagInfo> defaultFlagMap,
- Map<String, AconfigdFlagInfo> aconfigdFlagMap) {
-
- // Get all defaults from the default map. The mSettings may not contain
- // all flags, since it only contains updated flags.
- int diffNum = 0;
- for (Map.Entry<String, AconfigdFlagInfo> entry : defaultFlagMap.entrySet()) {
- String key = entry.getKey();
- AconfigdFlagInfo flag = entry.getValue();
-
- AconfigdFlagInfo aconfigdFlag = aconfigdFlagMap.get(key);
- if (aconfigdFlag == null) {
- Slog.w(LOG_TAG, String.format("Flag %s is missing from aconfigd", key));
- diffNum++;
- continue;
- }
- String diff = flag.dumpDiff(aconfigdFlag);
- if (!diff.isEmpty()) {
- Slog.w(
- LOG_TAG,
- String.format(
- "Flag %s is different in Settings and aconfig: %s", key, diff));
- diffNum++;
- }
- }
-
- for (String key : aconfigdFlagMap.keySet()) {
- if (defaultFlagMap.containsKey(key)) continue;
- Slog.w(LOG_TAG, String.format("Flag %s is missing from Settings", key));
- diffNum++;
- }
-
- String compareMarkerName = "aconfigd_marker/compare_diff_num";
- synchronized (mLock) {
- Setting markerSetting = mSettings.get(compareMarkerName);
- if (markerSetting == null) {
- markerSetting =
- new Setting(
- compareMarkerName,
- String.valueOf(diffNum),
- false,
- "aconfig",
- "aconfig");
- mSettings.put(compareMarkerName, markerSetting);
}
- markerSetting.value = String.valueOf(diffNum);
- }
-
- if (diffNum == 0) {
- Slog.w(LOG_TAG, "Settings and new storage have same flags.");
}
- return diffNum;
}
@GuardedBy("mLock")
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index cfd27c69032e..4fc3b873aa75 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -100,14 +100,4 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-}
-
-flag {
- name: "disable_bulk_compare"
- namespace: "core_experiments_team_internal"
- description: "Disable bulk comparison between DeviceConfig and aconfig storage."
- bug: "312444587"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
} \ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 276b206cd6a1..6e7576631147 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -1303,85 +1303,4 @@ public class SettingsStateTest {
assertFalse(flag3.getHasServerOverride());
assertFalse(flag3.getHasLocalOverride());
}
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DISABLE_BULK_COMPARE)
- public void testCompareFlagValueInNewStorage() {
- int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
- Object lock = new Object();
- SettingsState settingsState =
- new SettingsState(
- InstrumentationRegistry.getContext(),
- lock,
- mSettingsFile,
- configKey,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED,
- Looper.getMainLooper());
-
- AconfigdFlagInfo defaultFlag1 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag1")
- .setDefaultFlagValue("false")
- .setServerFlagValue("true")
- .setHasServerOverride(true)
- .setIsReadWrite(true)
- .build();
-
- AconfigdFlagInfo expectedFlag1 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag1")
- .setServerFlagValue("true")
- .setDefaultFlagValue("false")
- .setHasServerOverride(true)
- .setIsReadWrite(true)
- .build();
-
- Map<String, AconfigdFlagInfo> aconfigdMap = new HashMap<>();
- Map<String, AconfigdFlagInfo> defaultMap = new HashMap<>();
-
- defaultMap.put("com.android.flags.flag1", defaultFlag1);
- aconfigdMap.put("com.android.flags.flag1", expectedFlag1);
-
- int ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- assertEquals(0, ret);
-
- String value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("0", value);
-
- AconfigdFlagInfo defaultFlag2 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag2")
- .setDefaultFlagValue("false")
- .build();
- defaultMap.put("com.android.flags.flag2", defaultFlag2);
-
- ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- // missing from new storage
- assertEquals(1, ret);
- value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("1", value);
-
- AconfigdFlagInfo expectedFlag2 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag2")
- .setServerFlagValue("true")
- .setLocalFlagValue("true")
- .setDefaultFlagValue("false")
- .setHasServerOverride(true)
- .setHasLocalOverride(true)
- .build();
- aconfigdMap.put("com.android.flags.flag2", expectedFlag2);
- ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- // skip the server and local value comparison when the flag is read_only
- assertEquals(0, ret);
- value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("0", value);
- }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c3c5e874b907..a935aacacf95 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -423,6 +423,7 @@ android_library {
],
manifest: "AndroidManifest-res.xml",
flags_packages: [
+ "android.app.flags-aconfig",
"com_android_systemui_flags",
],
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 51ea5298fbb8..b53198d8ae98 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -406,7 +406,7 @@
android:killAfterRestore="false"
android:hardwareAccelerated="true"
android:label="@string/app_label"
- android:icon="@drawable/android15_patch_adaptive"
+ android:icon="@drawable/android16_patch_adaptive"
android:process="com.android.systemui"
android:supportsRtl="true"
android:theme="@style/Theme.SystemUI"
@@ -547,6 +547,12 @@
android:permission="android.permission.BIND_WALLPAPER"
android:exported="true" />
+ <service android:name=".wallpapers.GradientColorWallpaper"
+ android:featureFlag="android.app.enable_connected_displays_wallpaper"
+ android:singleUser="true"
+ android:permission="android.permission.BIND_WALLPAPER"
+ android:exported="true" />
+
<activity android:name=".tuner.TunerActivity"
android:enabled="false"
android:icon="@drawable/tuner"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 0f210e7e5e7b..b40a11469172 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -20,7 +20,10 @@
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.MANAGE_USERS"/>
- <application android:supportsRtl="true">
+ <application
+ android:supportsRtl="true"
+ android:allowBackup="true"
+ android:restoreAnyVersion="true">
<service
android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
android:exported="false"
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 1858c80ca901..088ec136f24e 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -23,6 +23,7 @@ package {
default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_visibility: [
"//visibility:override",
+ "//frameworks/base/libs/WindowManager/Shell:__subpackages__",
"//frameworks/base/packages/SystemUI:__subpackages__",
"//frameworks/libs/systemui/tracinglib:__subpackages__",
"//frameworks/base/services/accessibility:__subpackages__",
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 72b3d08396c6..fb21be4c3bd1 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -38,6 +38,16 @@ flag {
}
flag {
+ name: "floating_menu_display_cutout_support"
+ namespace: "accessibility"
+ description: "Makes FAB properly react to and avoid DisplayCutouts."
+ bug: "384399408"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "floating_menu_drag_to_hide"
namespace: "accessibility"
description: "Allows users to hide the FAB then use notification to dismiss or bring it back."
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 153f89284587..b33421d5d4ca 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1514,16 +1514,6 @@ flag {
}
flag {
- name: "sim_pin_talkback_fix_for_double_submit"
- namespace: "systemui"
- description: "The SIM PIN entry screens show the wrong message due"
- bug: "346932439"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "sim_pin_bouncer_reset"
namespace: "systemui"
description: "The SIM PIN bouncer does not close after unlocking"
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
index 2e8f92839fa6..4b8610884b05 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -185,16 +185,24 @@ public class ViewUIComponent implements UIComponent {
return;
}
ViewGroup.LayoutParams params = mView.getLayoutParams();
- if (params == null || params.width == 0 || params.height == 0) {
+ if (params == null) {
// layout pass didn't happen.
logD("draw: skipped - no layout");
return;
}
+
+ final Rect realBounds = getRealBounds();
+ if (realBounds.width() == 0 || realBounds.height() == 0) {
+ // bad bounds.
+ logD("draw: skipped - zero bounds");
+ return;
+ }
+
+
Canvas canvas = mSurface.lockHardwareCanvas();
// Clear the canvas first.
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
if (mVisibleOverride) {
- Rect realBounds = getRealBounds();
Rect renderBounds = getBounds();
canvas.translate(renderBounds.left, renderBounds.top);
canvas.scale(
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
index 311519122312..d08d859ec0d7 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
@@ -19,9 +19,11 @@ package com.android.compose.ui.graphics
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
@@ -30,14 +32,14 @@ import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.modifier.ModifierLocalModifierNode
import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.requireDensity
import androidx.compose.ui.node.requireGraphicsContext
-import androidx.compose.ui.node.requireLayoutCoordinates
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
@@ -48,17 +50,7 @@ import androidx.compose.ui.util.fastForEach
* The elements redirected to this container will be drawn above the content of this composable.
*/
fun Modifier.container(state: ContainerState): Modifier {
- return layout { measurable, constraints ->
- val p = measurable.measure(constraints)
- layout(p.width, p.height) {
- val coords = coordinates
- if (coords != null && !isLookingAhead) {
- state.lastCoords = coords
- }
-
- p.place(0, 0)
- }
- }
+ return onPlaced { state.lastOffsetInWindow = it.positionInWindow() }
.drawWithContent {
drawContent()
state.drawInOverlay(this)
@@ -91,7 +83,7 @@ fun Modifier.drawInContainer(
class ContainerState {
private var renderers = mutableStateListOf<LayerRenderer>()
- internal var lastCoords: LayoutCoordinates? = null
+ internal var lastOffsetInWindow by mutableStateOf(Offset.Zero)
internal fun onLayerRendererAttached(renderer: LayerRenderer) {
renderers.add(renderer)
@@ -142,7 +134,8 @@ internal class DrawInContainerNode(
var enabled: () -> Boolean = { true },
zIndex: Float = 0f,
var clipPath: (LayoutDirection, Density) -> Path? = { _, _ -> null },
-) : Modifier.Node(), DrawModifierNode, ModifierLocalModifierNode {
+) : Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode, ModifierLocalModifierNode {
+ private var lastOffsetInWindow by mutableStateOf(Offset.Zero)
var zIndex by mutableFloatStateOf(zIndex)
private inner class LayerWithRenderer(val layer: GraphicsLayer) : LayerRenderer {
@@ -152,11 +145,7 @@ internal class DrawInContainerNode(
override fun drawInOverlay(drawScope: DrawScope) {
if (enabled()) {
with(drawScope) {
- val containerCoords =
- checkNotNull(state.lastCoords) { "container is not placed" }
- val (x, y) =
- requireLayoutCoordinates().positionInWindow() -
- containerCoords.positionInWindow()
+ val (x, y) = lastOffsetInWindow - state.lastOffsetInWindow
val clipPath = clipPath(layoutDirection, requireDensity())
if (clipPath != null) {
clipPath(clipPath) { translate(x, y) { drawLayer(layer) } }
@@ -178,6 +167,10 @@ internal class DrawInContainerNode(
}
}
+ override fun onPlaced(coordinates: LayoutCoordinates) {
+ lastOffsetInWindow = coordinates.positionInWindow()
+ }
+
val layer: GraphicsLayer?
get() = layerWithRenderer?.layer
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 0054a4c899ec..439968590dad 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -168,7 +168,7 @@ private fun StandardLayout(viewModel: BouncerSceneContentViewModel, modifier: Mo
LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
FoldAware(
- modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 48.dp),
+ modifier = modifier.padding(top = 92.dp, bottom = 48.dp),
viewModel = viewModel,
aboveFold = {
Column(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 8321238b28b1..3d0354a578f7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -24,6 +24,8 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
@@ -35,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
@@ -45,6 +48,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Easings
@@ -212,23 +216,27 @@ fun PatternBouncer(
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var offset: Offset by remember { mutableStateOf(Offset.Zero) }
var scale: Float by remember { mutableStateOf(1f) }
+ // This is the size of the drawing area, in dips.
+ val dotDrawingArea =
+ remember(colCount, rowCount) {
+ DpSize(
+ // Because the width also includes spacing to the left and right of the leftmost and
+ // rightmost dots in the grid and because UX mocks specify the width without that
+ // spacing, the actual width needs to be defined slightly bigger than the UX mock
+ // width.
+ width = (262 * colCount / 2).dp,
+ // Because the height also includes spacing above and below the topmost and
+ // bottommost
+ // dots in the grid and because UX mocks specify the height without that spacing,
+ // the
+ // actual height needs to be defined slightly bigger than the UX mock height.
+ height = (262 * rowCount / 2).dp,
+ )
+ }
- Canvas(
- modifier
- .sysuiResTag("bouncer_pattern_root")
- // Because the width also includes spacing to the left and right of the leftmost and
- // rightmost dots in the grid and because UX mocks specify the width without that
- // spacing, the actual width needs to be defined slightly bigger than the UX mock width.
- .width((262 * colCount / 2).dp)
- // Because the height also includes spacing above and below the topmost and bottommost
- // dots in the grid and because UX mocks specify the height without that spacing, the
- // actual height needs to be defined slightly bigger than the UX mock height.
- .height((262 * rowCount / 2).dp)
- // Need to clip to bounds to make sure that the lines don't follow the input pointer
- // when it leaves the bounds of the dot grid.
- .clipToBounds()
- .onGloballyPositioned { coordinates -> gridCoordinates = coordinates }
- .thenIf(isInputEnabled) {
+ Box(
+ modifier =
+ modifier.fillMaxWidth().thenIf(isInputEnabled) {
Modifier.pointerInput(Unit) {
awaitEachGesture {
awaitFirstDown()
@@ -257,105 +265,125 @@ fun PatternBouncer(
inputPosition = change.position
change.position.minus(offset).div(scale).let {
viewModel.onDrag(
- xPx = it.x,
+ xPx =
+ it.x -
+ ((size.width - dotDrawingArea.width.roundToPx()) / 2),
yPx = it.y,
- containerSizePx = size.width,
+ containerSizePx = dotDrawingArea.width.roundToPx(),
)
}
}
}
}
- .motionTestValues {
- entryAnimationCompleted exportAs entryCompleted
- dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn
- dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp
- dotScalingAnimatables.map { it.value.value } exportAs dotScaling
- }
) {
- gridCoordinates?.let { nonNullCoordinates ->
- val containerSize = nonNullCoordinates.size
- if (containerSize.width <= 0 || containerSize.height <= 0) {
- return@let
- }
+ Canvas(
+ Modifier.sysuiResTag("bouncer_pattern_root")
+ .width(dotDrawingArea.width)
+ .height(dotDrawingArea.height)
+ // Need to clip to bounds to make sure that the lines don't follow the input pointer
+ // when it leaves the bounds of the dot grid.
+ .clipToBounds()
+ .align(Alignment.Center)
+ .onGloballyPositioned { coordinates -> gridCoordinates = coordinates }
+ .motionTestValues {
+ entryAnimationCompleted exportAs entryCompleted
+ dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn
+ dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp
+ dotScalingAnimatables.map { it.value.value } exportAs dotScaling
+ }
+ ) {
+ gridCoordinates?.let { nonNullCoordinates ->
+ val containerSize = nonNullCoordinates.size
+ if (containerSize.width <= 0 || containerSize.height <= 0) {
+ return@let
+ }
- val horizontalSpacing = containerSize.width.toFloat() / colCount
- val verticalSpacing = containerSize.height.toFloat() / rowCount
- val spacing = min(horizontalSpacing, verticalSpacing)
- val horizontalOffset =
- offset(
- availableSize = containerSize.width,
- spacingPerDot = spacing,
- dotCount = colCount,
- isCentered = true,
- )
- val verticalOffset =
- offset(
- availableSize = containerSize.height,
- spacingPerDot = spacing,
- dotCount = rowCount,
- isCentered = centerDotsVertically,
- )
- offset = Offset(horizontalOffset, verticalOffset)
- scale = (colCount * spacing) / containerSize.width
+ val horizontalSpacing = containerSize.width.toFloat() / colCount
+ val verticalSpacing = containerSize.height.toFloat() / rowCount
+ val spacing = min(horizontalSpacing, verticalSpacing)
+ val horizontalOffset =
+ offset(
+ availableSize = containerSize.width,
+ spacingPerDot = spacing,
+ dotCount = colCount,
+ isCentered = true,
+ )
+ val verticalOffset =
+ offset(
+ availableSize = containerSize.height,
+ spacingPerDot = spacing,
+ dotCount = rowCount,
+ isCentered = centerDotsVertically,
+ )
+ offset = Offset(horizontalOffset, verticalOffset)
+ scale = (colCount * spacing) / containerSize.width
- if (isAnimationEnabled) {
- // Draw lines between dots.
- selectedDots.forEachIndexed { index, dot ->
- if (index > 0) {
- val previousDot = selectedDots[index - 1]
- val lineFadeOutAnimationProgress =
- lineFadeOutAnimatables[previousDot]!!.value
- val startLerp = 1 - lineFadeOutAnimationProgress
- val from =
- pixelOffset(previousDot, spacing, horizontalOffset, verticalOffset)
- val to = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
- val lerpedFrom =
- Offset(
- x = from.x + (to.x - from.x) * startLerp,
- y = from.y + (to.y - from.y) * startLerp,
+ if (isAnimationEnabled) {
+ // Draw lines between dots.
+ selectedDots.forEachIndexed { index, dot ->
+ if (index > 0) {
+ val previousDot = selectedDots[index - 1]
+ val lineFadeOutAnimationProgress =
+ lineFadeOutAnimatables[previousDot]!!.value
+ val startLerp = 1 - lineFadeOutAnimationProgress
+ val from =
+ pixelOffset(previousDot, spacing, horizontalOffset, verticalOffset)
+ val to = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
+ val lerpedFrom =
+ Offset(
+ x = from.x + (to.x - from.x) * startLerp,
+ y = from.y + (to.y - from.y) * startLerp,
+ )
+ drawLine(
+ start = lerpedFrom,
+ end = to,
+ cap = StrokeCap.Round,
+ alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
+ color = lineColor,
+ strokeWidth = lineStrokeWidth,
)
- drawLine(
- start = lerpedFrom,
- end = to,
- cap = StrokeCap.Round,
- alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
- color = lineColor,
- strokeWidth = lineStrokeWidth,
- )
+ }
}
- }
- // Draw the line between the most recently-selected dot and the input pointer
- // position.
- inputPosition?.let { lineEnd ->
- currentDot?.let { dot ->
- val from = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
- val lineLength =
- sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
- drawLine(
- start = from,
- end = lineEnd,
- cap = StrokeCap.Round,
- alpha = lineAlpha(spacing, lineLength),
- color = lineColor,
- strokeWidth = lineStrokeWidth,
- )
+ // Draw the line between the most recently-selected dot and the input pointer
+ // position.
+ inputPosition?.let { lineEnd ->
+ currentDot?.let { dot ->
+ val from = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
+ val lineLength =
+ sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
+ drawLine(
+ start = from,
+ end = lineEnd,
+ cap = StrokeCap.Round,
+ alpha = lineAlpha(spacing, lineLength),
+ color = lineColor,
+ strokeWidth = lineStrokeWidth,
+ )
+ }
}
}
- }
- // Draw each dot on the grid.
- dots.forEach { dot ->
- val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot])
- val appearOffset =
- (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
- drawCircle(
- center =
- pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset),
- color =
- dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
- radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
- )
+ // Draw each dot on the grid.
+ dots.forEach { dot ->
+ val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot])
+ val appearOffset =
+ (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
+ drawCircle(
+ center =
+ pixelOffset(
+ dot,
+ spacing,
+ horizontalOffset,
+ verticalOffset + appearOffset,
+ ),
+ color =
+ dotColor.copy(
+ alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value
+ ),
+ radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
index f2edec657cd4..3ae50369e9e3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
@@ -27,9 +27,14 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.motionEventSpy
-import androidx.compose.ui.semantics.hideFromAccessibility
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@Composable
@@ -38,15 +43,38 @@ fun CommunalTouchableSurface(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit,
) {
-
+ val context = LocalContext.current
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier =
modifier
- // The touchable surface is hidden for accessibility because these actions are
- // already provided through custom accessibility actions.
- .semantics { hideFromAccessibility() }
+ .semantics {
+ contentDescription =
+ context.getString(
+ R.string.accessibility_content_description_for_communal_hub
+ )
+ customActions =
+ listOf(
+ CustomAccessibilityAction(
+ context.getString(
+ R.string.accessibility_action_label_close_communal_hub
+ )
+ ) {
+ viewModel.changeScene(
+ CommunalScenes.Blank,
+ "closed by accessibility",
+ )
+ true
+ },
+ CustomAccessibilityAction(
+ context.getString(R.string.accessibility_action_label_edit_widgets)
+ ) {
+ viewModel.onOpenWidgetEditor()
+ true
+ },
+ )
+ }
.combinedClickable(
onLongClick = viewModel::onLongClick,
onClick = viewModel::onClick,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index f052e60246d2..50bae8a094ad 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -31,10 +31,14 @@ import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
@@ -59,6 +63,8 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
@@ -96,13 +102,37 @@ constructor(
override fun ContentScope.Content(modifier: Modifier) {
val viewModel =
rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
+ val panelCornerRadius =
+ with(LocalDensity.current) { OverlayShade.Dimensions.PanelCornerRadius.toPx().toInt() }
+
+ // set the bounds to null when the QuickSettings overlay disappears
+ DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } }
OverlayShade(
panelAlignment = Alignment.TopEnd,
modifier = modifier,
onScrimClicked = viewModel::onScrimClicked,
) {
- Column {
+ Column(
+ modifier =
+ Modifier.onPlaced { coordinates ->
+ val boundsInWindow = coordinates.boundsInWindow()
+ val shadeScrimBounds =
+ ShadeScrimBounds(
+ left = boundsInWindow.left,
+ top = boundsInWindow.top,
+ right = boundsInWindow.right,
+ bottom = boundsInWindow.bottom,
+ )
+ val shape =
+ ShadeScrimShape(
+ bounds = shadeScrimBounds,
+ topRadius = 0,
+ bottomRadius = panelCornerRadius,
+ )
+ viewModel.onPanelShapeChanged(shape)
+ }
+ ) {
if (viewModel.showHeader) {
CollapsedShadeHeader(
viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -112,7 +142,6 @@ constructor(
statusBarIconController = statusBarIconController,
)
}
-
ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 4d1660e71c0a..e26e19d27417 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -79,8 +79,6 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
@Mock
private LatencyTracker mLatencyTracker;
@Mock
- private LiftToActivateListener mLiftToactivateListener;
- @Mock
private EmergencyButtonController mEmergencyButtonController;
private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
@Mock
@@ -122,7 +120,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
+ mKeyguardMessageAreaControllerFactory, mLatencyTracker,
mEmergencyButtonController, mFalsingCollector, featureFlags,
mSelectedUserInteractor, keyguardKeyboardInteractor, mBouncerHapticPlayer,
mUserActivityNotifier) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 4d2a6d9bd57a..142a2868ec14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -90,8 +90,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mLatencyTracker: LatencyTracker
- @Mock private lateinit var liftToActivateListener: LiftToActivateListener
-
@Mock private val mEmergencyButtonController: EmergencyButtonController? = null
private val falsingCollector: FalsingCollector = FalsingCollectorFake()
private val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
@@ -147,7 +145,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
mKeyguardSecurityCallback,
keyguardMessageAreaControllerFactory,
mLatencyTracker,
- liftToActivateListener,
mEmergencyButtonController,
falsingCollector,
postureController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 9cd52153eff6..c751a7db51dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -63,7 +63,6 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
@Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
@Mock private lateinit var latencyTracker: LatencyTracker
- @Mock private lateinit var liftToActivateListener: LiftToActivateListener
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var emergencyButtonController: EmergencyButtonController
@@ -100,7 +99,6 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
keyguardSecurityCallback,
messageAreaControllerFactory,
latencyTracker,
- liftToActivateListener,
telephonyManager,
falsingCollector,
emergencyButtonController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 3c229975eef5..c34682551eda 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -57,7 +57,6 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
@Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
@Mock private lateinit var latencyTracker: LatencyTracker
- @Mock private lateinit var liftToActivateListener: LiftToActivateListener
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var emergencyButtonController: EmergencyButtonController
@@ -95,7 +94,6 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
keyguardSecurityCallback,
messageAreaControllerFactory,
latencyTracker,
- liftToActivateListener,
telephonyManager,
falsingCollector,
emergencyButtonController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 56a97bb34172..fff6def52803 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.graphics.PointF;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
@@ -210,7 +211,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
mTouchHandler.onInterceptTouchEvent(mStubListView, stubUpEvent);
- verify(mMenuAnimationController).flingMenuThenSpringToEdge(anyFloat(), anyFloat(),
+ verify(mMenuAnimationController).flingMenuThenSpringToEdge(any(PointF.class), anyFloat(),
anyFloat());
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
new file mode 100644
index 000000000000..fbb0fee2419c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bouncer.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.SystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardBouncerRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var systemClock: SystemClock
+ @Mock private lateinit var bouncerLogger: TableLogBuffer
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ lateinit var underTest: KeyguardBouncerRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ object :
+ KeyguardBouncerRepositoryImpl(
+ systemClock,
+ testScope.backgroundScope,
+ bouncerLogger,
+ ) {
+ override fun isDebuggable(): Boolean = true
+ }
+ }
+
+ @Test
+ fun changingFlowValueTriggersLogging() =
+ testScope.runTest {
+ underTest.setPrimaryShow(true)
+ runCurrent()
+ Mockito.verify(bouncerLogger)
+ .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
+ }
+
+ @Test
+ fun primaryStartDisappearAnimation() =
+ testScope.runTest {
+ assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isFalse()
+
+ underTest.setPrimaryStartDisappearAnimation(Runnable {})
+ assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isTrue()
+
+ underTest.setPrimaryStartDisappearAnimation(null)
+ assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isFalse()
+
+ val disappearFlow by collectValues(underTest.primaryBouncerStartingDisappearAnimation)
+ underTest.setPrimaryStartDisappearAnimation(null)
+ assertThat(disappearFlow[0]).isNull()
+
+ // Now issue two in a row to make sure one is not dropped
+ underTest.setPrimaryStartDisappearAnimation(Runnable {})
+ underTest.setPrimaryStartDisappearAnimation(null)
+ assertThat(disappearFlow[1]).isNotNull()
+ assertThat(disappearFlow[2]).isNull()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index d5e1fae215c7..c1feca29906a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -105,7 +105,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
mSelectedUserInteractor,
faceAuthInteractor,
)
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(false)
whenever(repository.primaryBouncerShow.value).thenReturn(false)
whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
resources = context.orCreateTestableResources
@@ -199,7 +199,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testExpansion_fullyShown() {
whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
underTest.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
@@ -208,7 +207,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testExpansion_fullyHidden() {
whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
underTest.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setPrimaryShow(false)
verify(falsingCollector).onBouncerHidden()
@@ -307,7 +305,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
fun testIsFullShowing() {
whenever(repository.primaryBouncerShow.value).thenReturn(true)
whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
assertThat(underTest.isFullyShowing()).isTrue()
whenever(repository.primaryBouncerShow.value).thenReturn(false)
assertThat(underTest.isFullyShowing()).isFalse()
@@ -333,9 +330,9 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testIsAnimatingAway() {
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+ whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(true)
assertThat(underTest.isAnimatingAway()).isTrue()
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(false)
assertThat(underTest.isAnimatingAway()).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
index 791f1f2e1f26..6fdeb2b8ebb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
@@ -17,14 +17,17 @@ package com.android.systemui.clipboardoverlay
import android.content.ContentResolver
import android.content.Context
+import android.content.pm.UserInfo
import android.net.Uri
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER
import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.settings.FakeUserTracker
import java.io.IOException
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNull
@@ -36,44 +39,90 @@ import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class ClipboardImageLoaderTest : SysuiTestCase() {
@Mock private lateinit var mockContext: Context
@Mock private lateinit var mockContentResolver: ContentResolver
+ @Mock private lateinit var mockSecondaryContentResolver: ContentResolver
private lateinit var clipboardImageLoader: ClipboardImageLoader
+ private var fakeUserTracker: FakeUserTracker =
+ FakeUserTracker(userContentResolverProvider = { mockContentResolver })
+
+ private val userInfos = listOf(UserInfo(0, "system", 0), UserInfo(50, "secondary", 0))
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+
+ fakeUserTracker.set(userInfos, 0)
}
@Test
@Throws(IOException::class)
+ @DisableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
+ fun test_imageLoadSuccess_legacy() = runTest {
+ val testDispatcher = StandardTestDispatcher(this.testScheduler)
+ fakeUserTracker =
+ FakeUserTracker(userContentResolverProvider = { mockSecondaryContentResolver })
+ fakeUserTracker.set(userInfos, 1)
+
+ clipboardImageLoader =
+ ClipboardImageLoader(
+ mockContext,
+ fakeUserTracker,
+ testDispatcher,
+ CoroutineScope(testDispatcher),
+ )
+ val testUri = Uri.parse("testUri")
+ whenever<ContentResolver?>(mockContext.contentResolver)
+ .thenReturn(mockSecondaryContentResolver)
+ whenever(mockContext.resources).thenReturn(context.resources)
+
+ clipboardImageLoader.load(testUri)
+
+ verify(mockSecondaryContentResolver).loadThumbnail(eq(testUri), any(), any())
+ }
+
+ @Test
+ @Throws(IOException::class)
+ @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
fun test_imageLoadSuccess() = runTest {
val testDispatcher = StandardTestDispatcher(this.testScheduler)
+ fakeUserTracker =
+ FakeUserTracker(userContentResolverProvider = { mockSecondaryContentResolver })
+ fakeUserTracker.set(userInfos, 1)
+
clipboardImageLoader =
- ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+ ClipboardImageLoader(
+ mockContext,
+ fakeUserTracker,
+ testDispatcher,
+ CoroutineScope(testDispatcher),
+ )
val testUri = Uri.parse("testUri")
- whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
whenever(mockContext.resources).thenReturn(context.resources)
clipboardImageLoader.load(testUri)
- verify(mockContentResolver).loadThumbnail(eq(testUri), any(), any())
+ verify(mockSecondaryContentResolver).loadThumbnail(eq(testUri), any(), any())
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
@Throws(IOException::class)
fun test_imageLoadFailure() = runTest {
val testDispatcher = StandardTestDispatcher(this.testScheduler)
clipboardImageLoader =
- ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+ ClipboardImageLoader(
+ mockContext,
+ fakeUserTracker,
+ testDispatcher,
+ CoroutineScope(testDispatcher),
+ )
val testUri = Uri.parse("testUri")
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
whenever(mockContext.resources).thenReturn(context.resources)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index ca7e2032be93..26859b6e10f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -190,11 +190,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
lockDevice()
}
- if (shadeMode == ShadeMode.Dual) {
- assertThat(DualShade.isEnabled).isTrue()
- } else {
- assertThat(DualShade.isEnabled).isFalse()
- kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split)
+ if (shadeMode !is ShadeMode.Dual) {
+ kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode is ShadeMode.Split)
}
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
index d6daa794c45b..e19ea365fa1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
@@ -316,11 +316,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
lockDevice()
}
- if (shadeMode == ShadeMode.Dual) {
- assertThat(DualShade.isEnabled).isTrue()
- } else {
- assertThat(DualShade.isEnabled).isFalse()
- kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split)
+ if (shadeMode !is ShadeMode.Dual) {
+ kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode is ShadeMode.Split)
}
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index f2a6c11b872e..f66dee39166c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -16,11 +16,9 @@
package com.android.systemui.education.data.repository
-import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.SysuiTestableContext
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.education.data.model.EduDeviceConnectionTime
@@ -30,21 +28,16 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.google.common.truth.Truth.assertThat
-import java.io.File
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
import org.junit.Test
-import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-@Ignore("b/384284415")
class ContextualEducationRepositoryTest : SysuiTestCase() {
private lateinit var underTest: UserContextualEducationRepository
@@ -57,83 +50,74 @@ class ContextualEducationRepositoryTest : SysuiTestCase() {
private val testUserId = 1111
private val secondTestUserId = 1112
- // For deleting any test files created after the test
- @get:Rule val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
-
@Before
fun setUp() {
- // Create TestContext here because TemporaryFolder.create() is called in @Before. It is
- // needed before calling TemporaryFolder.newFolder().
- val testContext = TestContext(context, tmpFolder.newFolder())
underTest =
UserContextualEducationRepository(
- testContext,
+ context,
dsScopeProvider,
kosmos.mockEduInputManager,
- kosmos.testDispatcher
+ kosmos.testDispatcher,
)
underTest.setUser(testUserId)
}
@Test
- fun changeRetrievedValueForNewUser() =
- testScope.runTest {
- // Update data for old user.
- underTest.updateGestureEduModel(BACK) { it.copy(signalCount = 1) }
- val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
- assertThat(model?.signalCount).isEqualTo(1)
+ fun changeRetrievedValueForNewUser() = runTestAndClear {
+ // Update data for old user.
+ underTest.updateGestureEduModel(BACK) { it.copy(signalCount = 1) }
+ val model by testScope.collectLastValue(underTest.readGestureEduModelFlow(BACK))
+ assertThat(model?.signalCount).isEqualTo(1)
- // User is changed.
- underTest.setUser(secondTestUserId)
- // Assert count is 0 after user is changed.
- assertThat(model?.signalCount).isEqualTo(0)
- }
+ // User is changed.
+ underTest.setUser(secondTestUserId)
+ // Assert count is 0 after user is changed.
+ assertThat(model?.signalCount).isEqualTo(0)
+ }
@Test
- fun changeUserIdForNewUser() =
- testScope.runTest {
- val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
- assertThat(model?.userId).isEqualTo(testUserId)
- underTest.setUser(secondTestUserId)
- assertThat(model?.userId).isEqualTo(secondTestUserId)
- }
+ fun changeUserIdForNewUser() = runTestAndClear {
+ val model by testScope.collectLastValue(underTest.readGestureEduModelFlow(BACK))
+ assertThat(model?.userId).isEqualTo(testUserId)
+ underTest.setUser(secondTestUserId)
+ assertThat(model?.userId).isEqualTo(secondTestUserId)
+ }
@Test
- fun dataChangedOnUpdate() =
- testScope.runTest {
- val newModel =
- GestureEduModel(
- signalCount = 2,
- educationShownCount = 1,
- lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
- lastEducationTime = kosmos.fakeEduClock.instant(),
- usageSessionStartTime = kosmos.fakeEduClock.instant(),
- userId = testUserId,
- gestureType = BACK
- )
- underTest.updateGestureEduModel(BACK) { newModel }
- val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
- assertThat(model).isEqualTo(newModel)
- }
+ fun dataChangedOnUpdate() = runTestAndClear {
+ val newModel =
+ GestureEduModel(
+ signalCount = 2,
+ educationShownCount = 1,
+ lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
+ lastEducationTime = kosmos.fakeEduClock.instant(),
+ usageSessionStartTime = kosmos.fakeEduClock.instant(),
+ userId = testUserId,
+ gestureType = BACK,
+ )
+ underTest.updateGestureEduModel(BACK) { newModel }
+ val model by testScope.collectLastValue(underTest.readGestureEduModelFlow(BACK))
+ assertThat(model).isEqualTo(newModel)
+ }
@Test
- fun eduDeviceConnectionTimeDataChangedOnUpdate() =
- testScope.runTest {
- val newModel =
- EduDeviceConnectionTime(
- keyboardFirstConnectionTime = kosmos.fakeEduClock.instant(),
- touchpadFirstConnectionTime = kosmos.fakeEduClock.instant(),
- )
- underTest.updateEduDeviceConnectionTime { newModel }
- val model by collectLastValue(underTest.readEduDeviceConnectionTime())
- assertThat(model).isEqualTo(newModel)
- }
+ fun eduDeviceConnectionTimeDataChangedOnUpdate() = runTestAndClear {
+ val newModel =
+ EduDeviceConnectionTime(
+ keyboardFirstConnectionTime = kosmos.fakeEduClock.instant(),
+ touchpadFirstConnectionTime = kosmos.fakeEduClock.instant(),
+ )
+ underTest.updateEduDeviceConnectionTime { newModel }
+ val model by testScope.collectLastValue(underTest.readEduDeviceConnectionTime())
+ assertThat(model).isEqualTo(newModel)
+ }
- /** Test context which allows overriding getFilesDir path */
- private class TestContext(context: Context, private val folder: File) :
- SysuiTestableContext(context) {
- override fun getFilesDir(): File {
- return folder
+ private fun runTestAndClear(block: suspend () -> Unit) =
+ testScope.runTest {
+ try {
+ block()
+ } finally {
+ underTest.clear()
+ }
}
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
index 16f30feb7e3b..9c630ebb0ad2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
@@ -17,9 +17,6 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
import android.content.ComponentName
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.view.accessibility.Flags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.accessibility.AccessibilityShortcutController
@@ -43,44 +40,35 @@ class A11yShortcutAutoAddableListTest : SysuiTestCase() {
object : A11yShortcutAutoAddable.Factory {
override fun create(
spec: TileSpec,
- componentName: ComponentName
+ componentName: ComponentName,
): A11yShortcutAutoAddable {
return A11yShortcutAutoAddable(mock(), mock(), spec, componentName)
}
}
@Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOff_emptyResult() {
- val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
-
- assertThat(autoAddables).isEmpty()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOn_correctAutoAddables() {
+ fun getA11yShortcutAutoAddables_correctAutoAddables() {
val expected =
setOf(
factory.create(
TileSpec.create(ColorCorrectionTile.TILE_SPEC),
- AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME,
),
factory.create(
TileSpec.create(ColorInversionTile.TILE_SPEC),
- AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME,
),
factory.create(
TileSpec.create(OneHandedModeTile.TILE_SPEC),
- AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+ AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME,
),
factory.create(
TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
- AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+ AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
),
factory.create(
TileSpec.create(HearingDevicesTile.TILE_SPEC),
- AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME
+ AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME,
),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
deleted file mode 100644
index d0699aa12a43..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.pipeline.domain.autoaddable
-
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.view.accessibility.Flags
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.qs.ReduceBrightColorsController
-import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
-import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.ReduceBrightColorsTile
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestResult
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
-
- @Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
- @Captor
- private lateinit var reduceBrightColorsListenerCaptor:
- ArgumentCaptor<ReduceBrightColorsController.Listener>
-
- private lateinit var underTest: ReduceBrightColorsAutoAddable
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- }
-
- @Test
- fun notAvailable_strategyDisabled() =
- testWithFeatureAvailability(available = false) {
- assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun available_strategyIfNotAdded() =
- testWithFeatureAvailability(available = true) {
- assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
- }
-
- @Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun activated_addSignal() = testWithFeatureAvailability {
- val signal by collectLastValue(underTest.autoAddSignal(0))
- runCurrent()
-
- verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
-
- reduceBrightColorsListenerCaptor.value.onActivated(true)
-
- assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
- }
-
- @Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun notActivated_noSignal() = testWithFeatureAvailability {
- val signal by collectLastValue(underTest.autoAddSignal(0))
- runCurrent()
-
- verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
-
- reduceBrightColorsListenerCaptor.value.onActivated(false)
-
- assertThat(signal).isNull()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun available_a11yQsShortcutFlagEnabled_strategyDisabled() =
- testWithFeatureAvailability(available = true) {
- assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
- }
-
- private fun testWithFeatureAvailability(
- available: Boolean = true,
- body: suspend TestScope.() -> TestResult
- ) = runTest {
- underTest = ReduceBrightColorsAutoAddable(reduceBrightColorsController, available)
- body()
- }
-
- companion object {
- private val SPEC by lazy { TileSpec.create(ReduceBrightColorsTile.TILE_SPEC) }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
index 3faab5048fb4..6cd627c1d058 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
@@ -19,9 +19,6 @@ package com.android.systemui.qs.pipeline.domain.interactor
import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.view.accessibility.Flags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -50,21 +47,9 @@ import org.mockito.Mockito.mock
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class AccessibilityTilesInteractorTest : SysuiTestCase() {
- private val USER_0_INFO =
- UserInfo(
- 0,
- "zero",
- "",
- UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
- )
+ private val USER_0_INFO = UserInfo(0, "zero", "", UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL)
- private val USER_1_INFO =
- UserInfo(
- 1,
- "one",
- "",
- UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
- )
+ private val USER_1_INFO = UserInfo(1, "one", "", UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL)
private val USER_0_TILES = listOf(TileSpec.create(ColorInversionTile.TILE_SPEC))
private val USER_1_TILES = listOf(TileSpec.create(ColorCorrectionTile.TILE_SPEC))
@@ -94,20 +79,7 @@ class AccessibilityTilesInteractorTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun currentTilesChanged_a11yQsShortcutFlagOff_nothingHappen() =
- testScope.runTest {
- underTest = createInteractor()
-
- setTiles(USER_0_TILES)
- runCurrent()
-
- assertThat(a11yQsShortcutsRepository.notifyA11yManagerTilesChangedRequests).isEmpty()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun currentTilesChanged_a11yQsShortcutFlagOn_notifyAccessibilityRepository() =
+ fun currentTilesChanged_notifyAccessibilityRepository() =
testScope.runTest {
underTest = createInteractor()
@@ -123,8 +95,7 @@ class AccessibilityTilesInteractorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- fun userChanged_a11yQsShortcutFlagOn_notifyAccessibilityRepositoryWithCorrectTilesAndUser() =
+ fun userChanged_notifyAccessibilityRepositoryWithCorrectTilesAndUser() =
testScope.runTest {
underTest = createInteractor()
setTiles(USER_0_TILES)
@@ -163,7 +134,7 @@ class AccessibilityTilesInteractorTest : SysuiTestCase() {
return AccessibilityTilesInteractor(
a11yQsShortcutsRepository,
testDispatcher,
- testScope.backgroundScope
+ testScope.backgroundScope,
)
.apply { init(currentTilesInteractor) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index 7ab8ab93c024..dce110201e1d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -39,6 +39,9 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -143,6 +146,23 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
assertThat(underTest.showHeader).isFalse()
}
+ @Test
+ fun onPanelShapeChanged() =
+ testScope.runTest {
+ val actual by
+ collectLastValue(kosmos.notificationStackAppearanceInteractor.qsPanelShape)
+ val expected =
+ ShadeScrimShape(
+ bounds = ShadeScrimBounds(left = 10f, top = 0f, right = 710f, bottom = 600f),
+ topRadius = 0,
+ bottomRadius = 100,
+ )
+
+ underTest.onPanelShapeChanged(expected)
+
+ assertThat(expected).isEqualTo(actual)
+ }
+
private fun TestScope.lockDevice() {
val currentScene by collectLastValue(sceneInteractor.currentScene)
kosmos.powerInteractor.setAsleepForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
index fd9f5f02ee62..20dfd3e11947 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.shade.display
import android.view.Display
import android.view.Display.TYPE_EXTERNAL
+import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -28,11 +29,16 @@ import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.shade.domain.interactor.notificationElement
+import com.android.systemui.shade.domain.interactor.qsElement
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -50,9 +56,19 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
keyguardRepository,
testScope.backgroundScope,
shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked,
+ shadeInteractor = { kosmos.shadeInteractor },
+ { kosmos.qsElement },
+ { kosmos.notificationElement },
)
}
+ private fun createMotionEventForDisplay(displayId: Int, xCoordinate: Float = 0f): MotionEvent {
+ return mock<MotionEvent> {
+ on { getX() } doReturn xCoordinate
+ on { getDisplayId() } doReturn displayId
+ }
+ }
+
@Test
fun displayId_defaultToDefaultDisplay() {
val underTest = createUnderTest()
@@ -67,7 +83,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
assertThat(displayId).isEqualTo(2)
}
@@ -79,7 +95,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayIds by collectValues(underTest.displayId)
assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
// Never set, as 2 was not a display according to the repository.
assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
@@ -92,7 +108,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
assertThat(displayId).isEqualTo(2)
@@ -108,7 +124,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
assertThat(displayId).isEqualTo(2)
@@ -124,7 +140,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
assertThat(displayId).isEqualTo(2)
@@ -136,4 +152,48 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
assertThat(displayId).isEqualTo(2)
}
+
+ @Test
+ fun onStatusBarTouched_leftSide_intentSetToNotifications() =
+ testScope.runTest {
+ val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+ underTest.onStatusBarTouched(
+ createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f),
+ STATUS_BAR_WIDTH,
+ )
+
+ assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.notificationElement)
+ }
+
+ @Test
+ fun onStatusBarTouched_rightSide_intentSetToQs() =
+ testScope.runTest {
+ val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+ underTest.onStatusBarTouched(
+ createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.95f),
+ STATUS_BAR_WIDTH,
+ )
+
+ assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.qsElement)
+ }
+
+ @Test
+ fun onStatusBarTouched_nullAfterConsumed() =
+ testScope.runTest {
+ val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+ underTest.onStatusBarTouched(
+ createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f),
+ STATUS_BAR_WIDTH,
+ )
+ assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.notificationElement)
+
+ assertThat(underTest.consumeExpansionIntent()).isNull()
+ }
+
+ companion object {
+ private const val STATUS_BAR_WIDTH = 100
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
index 58396e7cef82..8aa8a50afcd4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
@@ -22,8 +22,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
-import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractorImpl.NotificationElement
-import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractorImpl.QSElement
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -52,7 +50,7 @@ class ShadeExpandedStateInteractorTest : SysuiTestCase() {
val element = currentlyExpandedElement.value
- assertThat(element).isInstanceOf(QSElement::class.java)
+ assertThat(element).isInstanceOf(QSShadeElement::class.java)
}
@Test
@@ -62,7 +60,7 @@ class ShadeExpandedStateInteractorTest : SysuiTestCase() {
val element = underTest.currentlyExpandedElement.value
- assertThat(element).isInstanceOf(NotificationElement::class.java)
+ assertThat(element).isInstanceOf(NotificationShadeElement::class.java)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 1df2553d0eb8..c3547bc5cc9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -74,7 +74,8 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
testScope.runTest {
val radius = MutableStateFlow(32)
val leftOffset = MutableStateFlow(0)
- val shape by collectLastValue(scrollViewModel.shadeScrimShape(radius, leftOffset))
+ val shape by
+ collectLastValue(scrollViewModel.notificationScrimShape(radius, leftOffset))
// When: receive scrim bounds
placeholderViewModel.onScrimBoundsChanged(
@@ -87,7 +88,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
bounds =
ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f),
topRadius = 32,
- bottomRadius = 0
+ bottomRadius = 0,
)
)
@@ -104,7 +105,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
bounds =
ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f),
topRadius = 24,
- bottomRadius = 0
+ bottomRadius = 0,
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index 06a2c5af2986..66ccf1822e21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -40,28 +41,39 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
private val underTest = kosmos.notificationStackAppearanceInteractor
@Test
- fun stackBounds() =
+ fun stackNotificationScrimBounds() =
testScope.runTest {
- val stackBounds by collectLastValue(underTest.shadeScrimBounds)
+ val stackBounds by collectLastValue(underTest.notificationShadeScrimBounds)
- val bounds1 =
- ShadeScrimBounds(
- top = 100f,
- bottom = 200f,
- )
- underTest.setShadeScrimBounds(bounds1)
+ val bounds1 = ShadeScrimBounds(top = 100f, bottom = 200f)
+ underTest.setNotificationShadeScrimBounds(bounds1)
assertThat(stackBounds).isEqualTo(bounds1)
- val bounds2 =
- ShadeScrimBounds(
- top = 200f,
- bottom = 300f,
- )
- underTest.setShadeScrimBounds(bounds2)
+ val bounds2 = ShadeScrimBounds(top = 200f, bottom = 300f)
+ underTest.setNotificationShadeScrimBounds(bounds2)
assertThat(stackBounds).isEqualTo(bounds2)
}
@Test
+ fun setQsPanelShape() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.qsPanelShape)
+
+ val expected1 =
+ ShadeScrimShape(
+ bounds = ShadeScrimBounds(top = 0f, bottom = 100f),
+ topRadius = 0,
+ bottomRadius = 10,
+ )
+ underTest.setQsPanelShape(expected1)
+ assertThat(actual).isEqualTo(expected1)
+
+ val expected2 = expected1.copy(topRadius = 10)
+ underTest.setQsPanelShape(expected2)
+ assertThat(expected2).isEqualTo(actual)
+ }
+
+ @Test
fun stackRounding() =
testScope.runTest {
val stackRounding by collectLastValue(underTest.shadeScrimRounding)
@@ -76,13 +88,17 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
}
@Test(expected = IllegalStateException::class)
- fun setStackBounds_withImproperBounds_throwsException() =
+ fun stackNotificationScrimBounds_withImproperBounds_throwsException() =
testScope.runTest {
- underTest.setShadeScrimBounds(
- ShadeScrimBounds(
- top = 100f,
- bottom = 99f,
- )
+ underTest.setNotificationShadeScrimBounds(ShadeScrimBounds(top = 100f, bottom = 99f))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setQsPanelShape_withImproperBounds_throwsException() =
+ testScope.runTest {
+ val invalidBounds = ShadeScrimBounds(top = 0f, bottom = -10f)
+ underTest.setQsPanelShape(
+ ShadeScrimShape(bounds = invalidBounds, topRadius = 10, bottomRadius = 10)
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index 4944c8bd0221..14e7cdc50227 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -38,12 +38,14 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
private val underTest by lazy { kosmos.notificationsPlaceholderViewModel }
@Test
- fun onBoundsChanged() =
+ fun onScrimBoundsChanged() =
kosmos.testScope.runTest {
val bounds = ShadeScrimBounds(left = 5f, top = 15f, right = 25f, bottom = 35f)
underTest.onScrimBoundsChanged(bounds)
val stackBounds by
- collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds)
+ collectLastValue(
+ kosmos.notificationStackAppearanceInteractor.notificationShadeScrimBounds
+ )
assertThat(stackBounds).isEqualTo(bounds)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 6feada1c9769..937f333b0065 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -46,6 +46,8 @@ class FakeHomeStatusBarViewModel(
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
+ override val shouldHomeStatusBarBeVisible = MutableStateFlow(false)
+
override val shouldShowOperatorNameView = MutableStateFlow(false)
override val isClockVisible =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index e95bc3378423..be4af868b740 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -575,6 +575,98 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
+ fun shouldHomeStatusBarBeVisible_keyguardNotGone_noHun_false() =
+ kosmos.runTest {
+ // Do not transition from keyguard. i.e., we don't call transitionKeyguardToGone()
+
+ // Nothing disabled
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+ val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun shouldHomeStatusBarBeVisible_keyguardNotGone_hun_true() =
+ kosmos.runTest {
+ // Keyguard gone
+ transitionKeyguardToGone()
+
+ // Nothing disabled
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+ // there is an active HUN
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun shouldHomeStatusBarBeVisible_keyguardGone_noHun_notInCamera_true() =
+ kosmos.runTest {
+ // Keyguard gone
+ transitionKeyguardToGone()
+
+ // Nothing disabled
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+ val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun shouldHomeStatusBarBeVisible_keyguardGone_hun_notInCamera_true() =
+ kosmos.runTest {
+ // Keyguard gone
+ transitionKeyguardToGone()
+
+ // Nothing disabled
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+ // there is an active HUN
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun shouldHomeStatusBarBeVisible_keyguardGone_noHun_inCamera_false() =
+ kosmos.runTest {
+ // Keyguard gone
+ transitionKeyguardToGone()
+
+ // Nothing disabled
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope = testScope,
+ )
+ kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+ val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
fun isClockVisible_allowedByDisableFlags_visible() =
kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt
index 20cc85f08b01..8608b0bf2f0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt
@@ -39,14 +39,12 @@ class StatusBarOperatorNameViewModelTest : SysuiTestCase() {
kosmos.runTest {
val intr1 = fakeMobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
val intr2 = fakeMobileIconsInteractor.getMobileConnectionInteractorForSubId(2)
- val invalidIntr = fakeMobileIconsInteractor.getMobileConnectionInteractorForSubId(-1)
// GIVEN default data subId is 1
fakeMobileIconsInteractor.defaultDataSubId.value = 1
intr1.carrierName.value = "Test Name 1"
intr2.carrierName.value = "Test Name 2"
- invalidIntr.carrierName.value = "default network name"
val latest by collectLastValue(underTest.operatorName)
@@ -56,8 +54,19 @@ class StatusBarOperatorNameViewModelTest : SysuiTestCase() {
assertThat(latest).isEqualTo("Test Name 2")
- fakeMobileIconsInteractor.defaultDataSubId.value = -1
+ fakeMobileIconsInteractor.defaultDataSubId.value = null
- assertThat(latest).isEqualTo("default network name")
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun operatorName_noDefaultDataSubId_null() =
+ kosmos.runTest {
+ // GIVEN defaultDataSubId is null
+ fakeMobileIconsInteractor.defaultDataSubId.value = null
+
+ val latest by collectLastValue(underTest.operatorName)
+
+ assertThat(latest).isNull()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 7802b921eae0..7c47264d448a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -36,8 +36,9 @@ import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
@@ -45,18 +46,13 @@ import com.android.systemui.statusbar.policy.data.repository.fakeZenModeReposito
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import java.time.Duration
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
class ZenModeInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val zenModeRepository = kosmos.fakeZenModeRepository
private val settingsRepository = kosmos.secureSettingsRepository
private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
@@ -65,166 +61,136 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun isZenAvailable_off() =
- testScope.runTest {
+ kosmos.runTest {
val isZenAvailable by collectLastValue(underTest.isZenAvailable)
deviceProvisioningRepository.setDeviceProvisioned(false)
- runCurrent()
-
assertThat(isZenAvailable).isFalse()
}
@Test
fun isZenAvailable_on() =
- testScope.runTest {
+ kosmos.runTest {
val isZenAvailable by collectLastValue(underTest.isZenAvailable)
deviceProvisioningRepository.setDeviceProvisioned(true)
- runCurrent()
-
assertThat(isZenAvailable).isTrue()
}
@Test
fun isZenModeEnabled_off() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
- runCurrent()
-
assertThat(enabled).isFalse()
}
@Test
fun isZenModeEnabled_alarms() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_ALARMS)
- runCurrent()
-
assertThat(enabled).isTrue()
}
@Test
fun isZenModeEnabled_importantInterruptions() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
-
assertThat(enabled).isTrue()
}
@Test
fun isZenModeEnabled_noInterruptions() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
- runCurrent()
-
assertThat(enabled).isTrue()
}
@Test
fun testIsZenModeEnabled_unknown() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
// this should fail if we ever add another zen mode type
zenModeRepository.updateZenMode(4)
- runCurrent()
-
assertThat(enabled).isFalse()
}
@Test
fun areNotificationsHiddenInShade_noPolicy() =
- testScope.runTest {
+ kosmos.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.updateNotificationPolicy(null)
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
assertThat(hidden).isFalse()
}
@Test
fun areNotificationsHiddenInShade_zenOffShadeSuppressed() =
- testScope.runTest {
+ kosmos.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.updateNotificationPolicy(
suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
)
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
- runCurrent()
assertThat(hidden).isFalse()
}
@Test
fun areNotificationsHiddenInShade_zenOnShadeNotSuppressed() =
- testScope.runTest {
+ kosmos.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.updateNotificationPolicy(
suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_STATUS_BAR
)
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
assertThat(hidden).isFalse()
}
@Test
fun areNotificationsHiddenInShade_zenOnShadeSuppressed() =
- testScope.runTest {
+ kosmos.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.updateNotificationPolicy(
suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
)
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
assertThat(hidden).isTrue()
}
@Test
fun shouldAskForZenDuration_falseForNonManualDnd() =
- testScope.runTest {
+ kosmos.runTest {
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT)
- runCurrent()
-
assertThat(underTest.shouldAskForZenDuration(TestModeBuilder.EXAMPLE)).isFalse()
}
@Test
fun shouldAskForZenDuration_changesWithSetting() =
- testScope.runTest {
+ kosmos.runTest {
val manualDnd = TestModeBuilder().makeManualDnd().setActive(true).build()
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
- runCurrent()
-
assertThat(underTest.shouldAskForZenDuration(manualDnd)).isFalse()
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT)
- runCurrent()
-
assertThat(underTest.shouldAskForZenDuration(manualDnd)).isTrue()
}
@Test
fun activateMode_nonManualDnd() =
- testScope.runTest {
+ kosmos.runTest {
val mode = TestModeBuilder().setActive(false).build()
zenModeRepository.addModes(listOf(mode))
settingsRepository.setInt(ZEN_DURATION, 60)
- runCurrent()
underTest.activateMode(mode)
assertThat(zenModeRepository.getMode(mode.id)?.isActive).isTrue()
@@ -233,16 +199,14 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun activateMode_usesCorrectDuration() =
- testScope.runTest {
+ kosmos.runTest {
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
- runCurrent()
underTest.activateMode(MANUAL_DND)
assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id)).isNull()
zenModeRepository.deactivateMode(MANUAL_DND)
settingsRepository.setInt(ZEN_DURATION, 60)
- runCurrent()
underTest.activateMode(MANUAL_DND)
assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id))
@@ -251,7 +215,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun deactivateAllModes_updatesCorrectModes() =
- testScope.runTest {
+ kosmos.runTest {
zenModeRepository.activateMode(MANUAL_DND)
zenModeRepository.addModes(
listOf(
@@ -267,72 +231,69 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun activeModes_computesMainActiveMode() =
- testScope.runTest {
+ kosmos.runTest {
val activeModes by collectLastValue(underTest.activeModes)
zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
-
- runCurrent()
assertThat(activeModes?.modeNames).hasSize(0)
assertThat(activeModes?.mainMode).isNull()
zenModeRepository.activateMode("Other")
- runCurrent()
assertThat(activeModes?.modeNames).containsExactly("Mode Other")
assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Other")
zenModeRepository.activateMode("Bedtime")
- runCurrent()
assertThat(activeModes?.modeNames)
.containsExactly("Mode Bedtime", "Mode Other")
.inOrder()
assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
zenModeRepository.deactivateMode("Other")
- runCurrent()
assertThat(activeModes?.modeNames).containsExactly("Mode Bedtime")
assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
zenModeRepository.deactivateMode("Bedtime")
- runCurrent()
assertThat(activeModes?.modeNames).hasSize(0)
assertThat(activeModes?.mainMode).isNull()
}
@Test
- fun getActiveModes_computesMainActiveMode() = runTest {
- zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
- zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
-
- var activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).hasSize(0)
- assertThat(activeModes.mainMode).isNull()
-
- zenModeRepository.activateMode("Other")
- activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).containsExactly("Mode Other")
- assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other")
-
- zenModeRepository.activateMode("Bedtime")
- activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).containsExactly("Mode Bedtime", "Mode Other").inOrder()
- assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
-
- zenModeRepository.deactivateMode("Other")
- activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).containsExactly("Mode Bedtime")
- assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
-
- zenModeRepository.deactivateMode("Bedtime")
- activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).hasSize(0)
- assertThat(activeModes.mainMode).isNull()
- }
+ fun getActiveModes_computesMainActiveMode() =
+ kosmos.runTest {
+ zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+ zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+
+ var activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).hasSize(0)
+ assertThat(activeModes.mainMode).isNull()
+
+ zenModeRepository.activateMode("Other")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).containsExactly("Mode Other")
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other")
+
+ zenModeRepository.activateMode("Bedtime")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames)
+ .containsExactly("Mode Bedtime", "Mode Other")
+ .inOrder()
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+
+ zenModeRepository.deactivateMode("Other")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).containsExactly("Mode Bedtime")
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+
+ zenModeRepository.deactivateMode("Bedtime")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).hasSize(0)
+ assertThat(activeModes.mainMode).isNull()
+ }
@Test
fun mainActiveMode_flows() =
- testScope.runTest {
+ kosmos.runTest {
val mainActiveMode by collectLastValue(underTest.mainActiveMode)
zenModeRepository.addModes(
@@ -355,51 +316,42 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
-
- runCurrent()
assertThat(mainActiveMode).isNull()
zenModeRepository.activateMode("Other")
- runCurrent()
assertThat(mainActiveMode?.name).isEqualTo("Mode Other")
assertThat(mainActiveMode?.icon?.key?.resId)
.isEqualTo(R.drawable.ic_zen_mode_type_other)
zenModeRepository.activateMode("Bedtime")
- runCurrent()
assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
assertThat(mainActiveMode?.icon?.key?.resId)
.isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
zenModeRepository.deactivateMode("Other")
- runCurrent()
assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
assertThat(mainActiveMode?.icon?.key?.resId)
.isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
zenModeRepository.deactivateMode("Bedtime")
- runCurrent()
assertThat(mainActiveMode).isNull()
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
fun dndMode_flows() =
- testScope.runTest {
+ kosmos.runTest {
val dndMode by collectLastValue(underTest.dndMode)
-
assertThat(dndMode!!.isActive).isFalse()
zenModeRepository.activateMode(MANUAL_DND)
- runCurrent()
-
assertThat(dndMode!!.isActive).isTrue()
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() =
- testScope.runTest {
+ kosmos.runTest {
val blockingMedia by
collectLastValue(
underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_MUSIC))
@@ -429,7 +381,6 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
- runCurrent()
assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active")
assertThat(blockingMedia!!.modeNames)
@@ -440,7 +391,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() =
- testScope.runTest {
+ kosmos.runTest {
val blockingAlarms by
collectLastValue(
underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_ALARM))
@@ -470,7 +421,6 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
- runCurrent()
assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active")
assertThat(blockingAlarms!!.modeNames)
@@ -481,7 +431,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
fun activeModesBlockingAlarms_hasModesWithPolicyBlockingSystem() =
- testScope.runTest {
+ kosmos.runTest {
val blockingSystem by
collectLastValue(
underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_SYSTEM))
@@ -511,7 +461,6 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
- runCurrent()
assertThat(blockingSystem!!.mainMode!!.name).isEqualTo("Blocks system, Active")
assertThat(blockingSystem!!.modeNames)
@@ -522,7 +471,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() =
- testScope.runTest {
+ kosmos.runTest {
val modesHidingNotifications by collectLastValue(underTest.modesHidingNotifications)
zenModeRepository.addModes(
@@ -554,7 +503,6 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
- runCurrent()
assertThat(modesHidingNotifications?.map { it.name })
.containsExactly("Has list suppression 1", "Has list suppression 2")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt
new file mode 100644
index 000000000000..4cbe33d66045
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.composable
+
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TutorialActionStateSaverTest : SysuiTestCase() {
+
+ private val saver = TutorialActionState.stateSaver()
+ private val saverScope: SaverScope =
+ object : SaverScope {
+ override fun canBeSaved(value: Any) = true
+ }
+
+ @Test
+ fun inProgressIsRestoredToNotStartedState() {
+ assertRestoredState(
+ savedState = InProgress(progress = 0f),
+ expectedRestoredState = NotStarted,
+ )
+ }
+
+ @Test
+ fun inProgressErrorIsRestoredToErrorState() {
+ assertRestoredState(
+ savedState = InProgressAfterError(InProgress(progress = 0f)),
+ expectedRestoredState = Error,
+ )
+ }
+
+ @Test
+ fun otherStatesAreRestoredToTheSameState() {
+ assertRestoredState(savedState = NotStarted, expectedRestoredState = NotStarted)
+ assertRestoredState(savedState = Error, expectedRestoredState = Error)
+ assertRestoredState(
+ savedState = Finished(successAnimation = R.raw.trackpad_home_success),
+ expectedRestoredState = Finished(successAnimation = R.raw.trackpad_home_success),
+ )
+ }
+
+ private fun assertRestoredState(
+ savedState: TutorialActionState,
+ expectedRestoredState: TutorialActionState,
+ ) {
+ val savedValue = with(saver) { saverScope.save(savedState) }
+ assertThat(saver.restore(savedValue!!)).isEqualTo(expectedRestoredState)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
new file mode 100644
index 000000000000..ba6ea9f5e8bb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.RectF
+import android.service.wallpaper.WallpaperService.Engine
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Surface
+import android.view.SurfaceHolder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class GradientColorWallpaperTest : SysuiTestCase() {
+
+ @Mock private lateinit var surfaceHolder: SurfaceHolder
+
+ @Mock private lateinit var surface: Surface
+
+ @Mock private lateinit var canvas: Canvas
+
+ @Mock private lateinit var mockContext: Context
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(surfaceHolder.surface).thenReturn(surface)
+ whenever(surfaceHolder.surfaceFrame).thenReturn(surfaceFrame)
+ whenever(surface.lockHardwareCanvas()).thenReturn(canvas)
+ whenever(mockContext.getColor(anyInt())).thenReturn(1)
+ }
+
+ private fun createGradientColorWallpaperEngine(): Engine {
+ val gradientColorWallpaper = GradientColorWallpaper()
+ val engine = spy(gradientColorWallpaper.onCreateEngine())
+ whenever(engine.displayContext).thenReturn(mockContext)
+ return engine
+ }
+
+ @Test
+ fun onSurfaceRedrawNeeded_shouldDrawInCanvas() {
+ val engine = createGradientColorWallpaperEngine()
+ engine.onCreate(surfaceHolder)
+
+ engine.onSurfaceRedrawNeeded(surfaceHolder)
+
+ verify(canvas).drawRect(any<RectF>(), any<Paint>())
+ }
+
+ private companion object {
+ val surfaceFrame = Rect(0, 0, 100, 100)
+ }
+}
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive.xml
new file mode 100644
index 000000000000..277df47438e3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/android16_patch_adaptive_background"/>
+ <foreground android:drawable="@drawable/android16_patch_adaptive_foreground"/>
+ <monochrome android:drawable="@drawable/android16_patch_monochrome"/>
+</adaptive-icon>
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml
new file mode 100644
index 000000000000..17c2b927f4fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml
@@ -0,0 +1,245 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <group>
+ <clip-path
+ android:pathData="M0,0h108v108h-108z"/>
+ <path
+ android:pathData="M73,54L54,35L35,54L54,73L73,54Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M44.5,44.5L54,44.5L44.5,54L44.5,44.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M63.5,63.5L54,63.5L63.5,54L63.5,63.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,54L54,44.5L63.5,54L54,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,44.5L54,35L63.5,44.5L54,44.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,63.5L54,73L44.5,63.5L54,63.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M63.5,54L63.5,44.5L73,54L63.5,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M44.5,54L44.5,63.5L35,54L44.5,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,54L54,63.5L44.5,54L54,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M82.5,25.5L82.5,35L73,25.5L82.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,44.5L63.5,35L73,44.5L63.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,35L82.5,35L73,44.5L73,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,35L92,35L82.5,44.5L82.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,35L54,35L63.5,25.5L63.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,44.5L82.5,44.5L73,54L73,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,25.5L63.5,25.5L73,16L73,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,35L63.5,35L73,25.5L73,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,63.5L82.5,73L73,63.5L82.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,82.5L63.5,73L73,82.5L63.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,73L82.5,73L73,82.5L73,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,73L92,73L82.5,82.5L82.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,73L54,73L63.5,63.5L63.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,82.5L82.5,82.5L73,92L73,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,63.5L63.5,63.5L73,54L73,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,73L63.5,73L73,63.5L73,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,63.5L44.5,73L35,63.5L44.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,82.5L25.5,73L35,82.5L25.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,73L44.5,73L35,82.5L35,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,73L54,73L44.5,82.5L44.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,73L16,73L25.5,63.5L25.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,82.5L44.5,82.5L35,92L35,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,63.5L25.5,63.5L35,54L35,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,73L25.5,73L35,63.5L35,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,25.5L44.5,35L35,25.5L44.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,44.5L25.5,35L35,44.5L25.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,35L44.5,35L35,44.5L35,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,35L54,35L44.5,44.5L44.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,35L16,35L25.5,25.5L25.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,44.5L44.5,44.5L35,54L35,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,25.5L25.5,25.5L35,16L35,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,35L25.5,35L35,25.5L35,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,25.5L54,25.5L63.5,16L63.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,6.5L54,6.5L44.5,16L44.5,6.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,16L54,25.5L44.5,16L54,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,25.5L54,35L44.5,25.5L54,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,6.5L54,-3L63.5,6.5L54,6.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,16L44.5,25.5L35,16L44.5,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,16L63.5,6.5L73,16L63.5,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,16L54,6.5L63.5,16L54,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M101.5,63.5L92,63.5L101.5,54L101.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,44.5L92,44.5L82.5,54L82.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,54L92,63.5L82.5,54L92,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,63.5L92,73L82.5,63.5L92,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,44.5L92,35L101.5,44.5L92,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,54L82.5,63.5L73,54L82.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M101.5,54L101.5,44.5L111,54L101.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,54L92,44.5L101.5,54L92,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,101.5L54,101.5L63.5,92L63.5,101.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,82.5L54,82.5L44.5,92L44.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,92L54,101.5L44.5,92L54,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,101.5L54,111L44.5,101.5L54,101.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,82.5L54,73L63.5,82.5L54,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,92L44.5,101.5L35,92L44.5,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,92L63.5,82.5L73,92L63.5,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,92L54,82.5L63.5,92L54,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,63.5L16,63.5L25.5,54L25.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M6.5,44.5L16,44.5L6.5,54L6.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,54L16,63.5L6.5,54L16,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,63.5L16,73L6.5,63.5L16,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,44.5L16,35L25.5,44.5L16,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M6.5,54L6.5,63.5L-3,54L6.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,54L25.5,44.5L35,54L25.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,54L16,44.5L25.5,54L16,54Z"
+ android:fillColor="#16161D"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml
new file mode 100644
index 000000000000..4c2932399c1a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+ android:fillColor="#C6FF00"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.973 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.557C62.817,58.796 63.205,58.796 63.444,58.557L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/android16_patch_monochrome.xml b/packages/SystemUI/res/drawable/android16_patch_monochrome.xml
new file mode 100644
index 000000000000..608d5ea6ee48
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_monochrome.xml
@@ -0,0 +1,113 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:strokeWidth="1"
+ android:pathData="M54.707,35.707L72.293,53.293A1,1 102.155,0 1,72.293 54.707L54.707,72.293A1,1 0,0 1,53.293 72.293L35.707,54.707A1,1 0,0 1,35.707 53.293L53.293,35.707A1,1 0,0 1,54.707 35.707z"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M55.237,35.177L72.823,52.763A1.75,1.75 67.835,0 1,72.823 55.237L55.237,72.823A1.75,1.75 77.684,0 1,52.763 72.823L35.177,55.237A1.75,1.75 0,0 1,35.177 52.763L52.763,35.177A1.75,1.75 0,0 1,55.237 35.177z"
+ android:strokeWidth="1.5"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M44.5,44.5h19v19h-19z"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M54,44.5l9.5,9.5l-9.5,9.5l-9.5,-9.5z"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M54,35V73"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M73,54L35,54"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M33.576,31.135l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M31.146,65.966l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M26.718,56l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M31.146,48l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M41.925,34.374l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M63.146,71l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M48.567,74.553l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M51.146,26l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M72.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M76.531,36.417l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M58.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M68.419,36.978l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M74.252,64.034l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M71.437,76.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M42.984,69.38l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M82.437,51.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.972 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.556C62.817,58.796 63.205,58.796 63.444,58.556L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index b3f32a2863a8..5e8a8a570466 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -34,13 +34,14 @@
android:layout_height="match_parent"
android:layout_gravity="center_vertical">
- <TextView
+ <com.android.systemui.qs.BuildTextView
android:id="@+id/build"
android:layout_width="0dp"
android:layout_height="match_parent"
android:paddingEnd="4dp"
android:layout_weight="1"
- android:clickable="true"
+ android:marqueeRepeatLimit="1"
+ android:clickable="false"
android:ellipsize="marquee"
android:focusable="true"
android:gravity="center_vertical"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 6eb7b730e105..c5f468e731f5 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -14,8 +14,8 @@
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dp"
- android:layout_height="0dp"
+ android:layout_width="@dimen/volume_dialog_slider_width"
+ android:layout_height="match_parent"
android:maxHeight="@dimen/volume_dialog_slider_height">
<com.google.android.material.slider.Slider
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a8ee60973586..e785af8e36c9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3815,7 +3815,7 @@
user what action they need to take in the customization dialog to assign a new custom shortcut.
The shortcut customize dialog allows users to add/remove custom shortcuts
[CHAR LIMIT=NONE] -->
- <string name="shortcut_customize_mode_add_shortcut_description">Press key to assign shortcut</string>
+ <string name="shortcut_customize_mode_add_shortcut_description">To create this shortcut, press the Action key and one or more other keys together</string>
<!-- Sub title at the top of the remove custom shortcut dialog. Explains to the user what action
they're about to take when they click remove shortcut. The shortcut customize dialog allows
users to add/remove custom shortcuts
@@ -3912,7 +3912,7 @@
<!-- Error message displayed when the user select a key combination that is already in use while
assigning a new custom key combination to a shortcut in shortcut helper. The helper is a
component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
- <string name="shortcut_customizer_key_combination_in_use_error_message">Key combination already in use. Try another key.</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message">Key combination already in use. Try another combination.</string>
<!-- Generic error message displayed when the user selected key combination cannot be used as
custom keyboard shortcut in shortcut helper. The helper is a component that shows the user
which keyboard shortcuts they can use and allows users to customize their keyboard
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index ff788484c819..ec97b8a96c1f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -208,7 +208,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private final InputMethodManager mInputMethodManager;
private final DelayableExecutor mMainExecutor;
private final Resources mResources;
- private final LiftToActivateListener mLiftToActivateListener;
private final TelephonyManager mTelephonyManager;
private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final FalsingCollector mFalsingCollector;
@@ -227,7 +226,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
LatencyTracker latencyTracker,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
- @Main Resources resources, LiftToActivateListener liftToActivateListener,
+ @Main Resources resources,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController.Factory emergencyButtonControllerFactory,
DevicePostureController devicePostureController,
@@ -244,7 +243,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mInputMethodManager = inputMethodManager;
mMainExecutor = mainExecutor;
mResources = resources;
- mLiftToActivateListener = liftToActivateListener;
mTelephonyManager = telephonyManager;
mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mFalsingCollector = falsingCollector;
@@ -284,7 +282,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
+ emergencyButtonController, mFalsingCollector,
mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
mUiEventLogger, mKeyguardKeyboardInteractor, mBouncerHapticPlayer,
mUserActivityNotifier);
@@ -292,14 +290,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
+ mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
+ mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier
);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index e22736b69213..622b67f25da3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -297,13 +297,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
? mDisappearAnimationUtilsLocked
: mDisappearAnimationUtils;
- android.util.Log.i("KeyguardPINView", "startDisappearAnimation: " + finishRunnable);
disappearAnimationUtils.createAnimation(
this, 0, 200, mDisappearYTranslation, false,
mDisappearAnimationUtils.getInterpolator(), () -> {
if (finishRunnable != null) {
- android.util.Log.i("KeyguardPINView",
- "startDisappearAnimation, invoking run()");
finishRunnable.run();
}
},
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index d999994a3312..7f176de547bc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -34,7 +34,6 @@ import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
-import com.android.systemui.Flags;
import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
@@ -44,7 +43,6 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
- private final LiftToActivateListener mLiftToActivateListener;
private final FalsingCollector mFalsingCollector;
private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
protected PasswordTextView mPasswordEntry;
@@ -73,7 +71,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
- LiftToActivateListener liftToActivateListener,
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
FeatureFlags featureFlags,
@@ -85,7 +82,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor,
bouncerHapticPlayer, userActivityNotifier);
- mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
@@ -151,10 +147,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
verifyPasswordAndUnlock();
}
});
-
- if (!Flags.simPinTalkbackFixForDoubleSubmit()) {
- okButton.setOnHoverListener(mLiftToActivateListener);
- }
}
if (pinInputFieldStyledFocusState()) {
collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index b159a70066ce..9ae4cc6a4b4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -56,7 +56,7 @@ public class KeyguardPinViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ LatencyTracker latencyTracker,
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
DevicePostureController postureController, FeatureFlags featureFlags,
@@ -65,7 +65,7 @@ public class KeyguardPinViewController
BouncerHapticPlayer bouncerHapticPlayer,
UserActivityNotifier userActivityNotifier) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 52c93f72206b..24f77d77dbe1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -91,7 +91,7 @@ public class KeyguardSimPinViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ LatencyTracker latencyTracker,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
@@ -99,7 +99,7 @@ public class KeyguardSimPinViewController
BouncerHapticPlayer bouncerHapticPlayer,
UserActivityNotifier userActivityNotifier) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 9adc5bae1ada..e17e8cc05f7e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -89,7 +89,7 @@ public class KeyguardSimPukViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ LatencyTracker latencyTracker,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
@@ -97,7 +97,7 @@ public class KeyguardSimPukViewController
BouncerHapticPlayer bouncerHapticPlayer,
UserActivityNotifier userActivityNotifier) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
deleted file mode 100644
index 425e50ed6397..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-
-import javax.inject.Inject;
-
-/**
- * Hover listener that implements lift-to-activate interaction for
- * accessibility. May be added to multiple views.
- */
-class LiftToActivateListener implements View.OnHoverListener {
- /** Manager used to query accessibility enabled state. */
- private final AccessibilityManager mAccessibilityManager;
-
- private boolean mCachedClickableState;
-
- @Inject
- LiftToActivateListener(AccessibilityManager accessibilityManager) {
- mAccessibilityManager = accessibilityManager;
- }
-
- @Override
- public boolean onHover(View v, MotionEvent event) {
- // When touch exploration is turned on, lifting a finger while
- // inside the view bounds should perform a click action.
- if (mAccessibilityManager.isEnabled()
- && mAccessibilityManager.isTouchExplorationEnabled()) {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- // Lift-to-type temporarily disables double-tap
- // activation by setting the view as not clickable.
- mCachedClickableState = v.isClickable();
- v.setClickable(false);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
- && (x < v.getWidth() - v.getPaddingRight())
- && (y < v.getHeight() - v.getPaddingBottom())) {
- v.performClick();
- }
- v.setClickable(mCachedClickableState);
- break;
- }
- }
-
- // Pass the event to View.onHoverEvent() to handle accessibility.
- v.onHoverEvent(event);
-
- // Consume the event so it doesn't fall through to other views.
- return true;
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 5b433464c1c6..5cba464fc24c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -609,15 +609,12 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
final WindowMagnificationController windowMagnificationController =
mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
- boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
- if (isWindowMagnifierActivated) {
- windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
- }
+ windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
if (shown) {
mA11yLogger.logWithPosition(
MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED,
- isWindowMagnifierActivated
+ windowMagnificationController.isActivated()
? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
: ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 08d3e17c03d7..1587ab16fc38 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1519,12 +1519,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
void updateDragHandleResourcesIfNeeded(boolean settingsPanelIsShown) {
+ mSettingsPanelVisibility = settingsPanelIsShown;
+
if (!isActivated()) {
return;
}
- mSettingsPanelVisibility = settingsPanelIsShown;
-
mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown
? R.drawable.accessibility_window_magnification_drag_handle_background_change_inset
: R.drawable.accessibility_window_magnification_drag_handle_background_inset));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 030d147e21c3..edbede8fa865 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -24,6 +24,7 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.view.DisplayCutout;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.OvershootInterpolator;
@@ -197,7 +198,7 @@ class MenuAnimationController {
constrainPositionAndUpdate(position, /* writeToPosition = */ true);
}
- void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
+ void flingMenuThenSpringToEdge(PointF position, float velocityX, float velocityY) {
final boolean shouldMenuFlingLeft = isOnLeftSide()
? velocityX < ESCAPE_VELOCITY
: velocityX < -ESCAPE_VELOCITY;
@@ -205,9 +206,17 @@ class MenuAnimationController {
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
final float finalPositionX = shouldMenuFlingLeft
? draggableBounds.left : draggableBounds.right;
-
+ final DisplayCutout displayCutout = mMenuViewAppearance.getDisplayCutout();
+ final float finalPositionY =
+ (displayCutout == null) ? position.y
+ : mMenuViewAppearance.avoidVerticalDisplayCutout(
+ position.y, draggableBounds,
+ shouldMenuFlingLeft
+ ? displayCutout.getBoundingRectLeft()
+ : displayCutout.getBoundingRectRight()
+ );
final float minimumVelocityToReachEdge =
- (finalPositionX - x) * (FLING_FRICTION_SCALAR * DEFAULT_FRICTION);
+ (finalPositionX - position.x) * (FLING_FRICTION_SCALAR * DEFAULT_FRICTION);
final float startXVelocity = shouldMenuFlingLeft
? Math.min(minimumVelocityToReachEdge, velocityX)
@@ -219,11 +228,19 @@ class MenuAnimationController {
createSpringForce(),
finalPositionX);
- flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
- velocityY,
- FLING_FRICTION_SCALAR,
- createSpringForce(),
- /* finalPosition= */ null);
+ if (com.android.systemui.Flags.floatingMenuDisplayCutoutSupport()) {
+ flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
+ velocityY,
+ FLING_FRICTION_SCALAR,
+ createSpringForce(),
+ (finalPositionY != position.y) ? finalPositionY : null);
+ } else {
+ flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
+ velocityY,
+ FLING_FRICTION_SCALAR,
+ createSpringForce(),
+ /* finalPosition= */ null);
+ }
}
private void flingThenSpringMenuWith(DynamicAnimation.ViewProperty property, float velocity,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index 9511e3769a8d..aca020d235be 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -105,7 +105,8 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent)
== empty) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
- mMenuAnimationController.flingMenuThenSpringToEdge(endX,
+ mMenuAnimationController.flingMenuThenSpringToEdge(
+ new PointF(endX, mMenuTranslationDown.y + dy),
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
mMenuAnimationController.fadeOutIfEnabled();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index a700cbef2e16..bd3dfe049587 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -28,12 +28,14 @@ import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.view.DisplayCutout;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import androidx.annotation.DimenRes;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -291,7 +293,7 @@ class MenuViewAppearance {
final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
final WindowInsets windowInsets = windowMetrics.getWindowInsets();
final Insets insets = windowInsets.getInsetsIgnoringVisibility(
- WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+ WindowInsets.Type.systemBars());
final Rect bounds = new Rect(windowMetrics.getBounds());
bounds.left += insets.left;
@@ -302,6 +304,37 @@ class MenuViewAppearance {
return bounds;
}
+ DisplayCutout getDisplayCutout() {
+ return mWindowManager.getCurrentWindowMetrics().getWindowInsets().getDisplayCutout();
+ }
+
+ float avoidVerticalDisplayCutout(float y, Rect bounds, Rect cutout) {
+ int menuHeight = calculateActualMenuHeight();
+ return avoidVerticalDisplayCutout(y, menuHeight, bounds, cutout);
+ }
+
+ @VisibleForTesting
+ public static float avoidVerticalDisplayCutout(
+ float y, float menuHeight, Rect bounds, Rect cutout) {
+ if (cutout.top > y + menuHeight || cutout.bottom < y) {
+ return y;
+ }
+
+ boolean topAvailable = cutout.top - bounds.top >= menuHeight;
+ boolean bottomAvailable = bounds.bottom - cutout.bottom >= menuHeight;
+ boolean topOrBottom;
+ if (!topAvailable && !bottomAvailable) {
+ return y;
+ } else if (topAvailable && !bottomAvailable) {
+ topOrBottom = true;
+ } else if (!topAvailable && bottomAvailable) {
+ topOrBottom = false;
+ } else {
+ topOrBottom = y + menuHeight * 0.5f < cutout.centerY();
+ }
+ return (topOrBottom) ? cutout.top - menuHeight : cutout.bottom;
+ }
+
boolean isMenuOnLeftSide() {
return mPercentagePosition.getPercentageX() < 0.5f;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 7a674e2fa6f1..81095220b4a6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -21,17 +21,12 @@ import static android.view.WindowInsets.Type.ime;
import static androidx.core.view.WindowInsetsCompat.Type;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
-import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
@@ -39,7 +34,6 @@ import android.app.NotificationManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -174,46 +168,13 @@ class MenuViewLayer extends FrameLayout implements
final Runnable mDismissMenuAction = new Runnable() {
@Override
public void run() {
- if (android.view.accessibility.Flags.a11yQsShortcut()) {
- mAccessibilityManager.enableShortcutsForTargets(
- /* enable= */ false,
- ShortcutConstants.UserShortcutType.SOFTWARE,
- new ArraySet<>(
- mAccessibilityManager.getAccessibilityShortcutTargets(SOFTWARE)),
- mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)
- );
- } else {
- mSecureSettings.putStringForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
- UserHandle.USER_CURRENT);
-
- final List<ComponentName> hardwareKeyShortcutComponents =
- mAccessibilityManager.getAccessibilityShortcutTargets(HARDWARE)
- .stream()
- .map(ComponentName::unflattenFromString)
- .toList();
-
- // Should disable the corresponding service when the fragment type is
- // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
- final List<AccessibilityServiceInfo> serviceInfoList =
- mAccessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
- serviceInfoList.forEach(info -> {
- if (getAccessibilityServiceFragmentType(info) != INVISIBLE_TOGGLE) {
- return;
- }
-
- final ComponentName serviceComponentName = info.getComponentName();
- if (hardwareKeyShortcutComponents.contains(serviceComponentName)) {
- return;
- }
-
- setAccessibilityServiceState(
- getContext(), serviceComponentName, /* enabled= */ false,
- mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
- });
- }
-
+ mAccessibilityManager.enableShortcutsForTargets(
+ /* enable= */ false,
+ ShortcutConstants.UserShortcutType.SOFTWARE,
+ new ArraySet<>(
+ mAccessibilityManager.getAccessibilityShortcutTargets(SOFTWARE)),
+ mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)
+ );
mFloatingMenu.hide();
}
};
@@ -516,7 +477,7 @@ class MenuViewLayer extends FrameLayout implements
return;
}
mMenuAnimationController.flingMenuThenSpringToEdge(
- mMenuView.getMenuPosition().x, 100f, 0f);
+ mMenuView.getMenuPosition(), 100f, 0f);
Intent intent = getIntentForEditScreen();
PackageManager packageManager = getContext().getPackageManager();
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index 22b2888a51a9..a44778657d25 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.data.repository
+import android.annotation.SuppressLint
import android.os.Build
import android.util.Log
import com.android.keyguard.KeyguardSecurityModel
@@ -51,7 +52,11 @@ interface KeyguardBouncerRepository {
val primaryBouncerShow: StateFlow<Boolean>
val primaryBouncerShowingSoon: StateFlow<Boolean>
val primaryBouncerStartingToHide: StateFlow<Boolean>
- val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+ val primaryBouncerStartingDisappearAnimation: MutableSharedFlow<Runnable?>
+
+ fun isPrimaryBouncerStartingDisappearAnimation(): Boolean
+
+ fun isDebuggable(): Boolean
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
val primaryBouncerScrimmed: StateFlow<Boolean>
@@ -128,7 +133,7 @@ interface KeyguardBouncerRepository {
}
@SysUISingleton
-class KeyguardBouncerRepositoryImpl
+open class KeyguardBouncerRepositoryImpl
@Inject
constructor(
private val clock: SystemClock,
@@ -144,9 +149,19 @@ constructor(
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
- private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+
+ @SuppressLint("SharedFlowCreation")
override val primaryBouncerStartingDisappearAnimation =
- _primaryBouncerDisappearAnimation.asStateFlow()
+ MutableSharedFlow<Runnable?>(extraBufferCapacity = 2, replay = 1)
+
+ override fun isPrimaryBouncerStartingDisappearAnimation(): Boolean {
+ val replayCache = primaryBouncerStartingDisappearAnimation.replayCache
+ return if (!replayCache.isEmpty()) {
+ replayCache.last() != null
+ } else {
+ false
+ }
+ }
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
private val _primaryBouncerScrimmed = MutableStateFlow(false)
@@ -177,6 +192,7 @@ constructor(
_keyguardAuthenticatedPrimaryAuth.asSharedFlow()
/** Whether the user requested to show the bouncer when device is already authenticated */
+ @SuppressLint("SharedFlowCreation")
private val _userRequestedBouncerWhenAlreadyAuthenticated = MutableSharedFlow<Int>()
override val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
_userRequestedBouncerWhenAlreadyAuthenticated.asSharedFlow()
@@ -226,7 +242,7 @@ constructor(
}
override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
- _primaryBouncerDisappearAnimation.value = runnable
+ primaryBouncerStartingDisappearAnimation.tryEmit(runnable)
}
override fun setPanelExpansion(panelExpansion: Float) {
@@ -265,9 +281,11 @@ constructor(
_lastShownSecurityMode.value = securityMode
}
+ override fun isDebuggable() = Build.IS_DEBUGGABLE
+
/** Sets up logs for state flows. */
private fun setUpLogging() {
- if (!Build.IS_DEBUGGABLE) {
+ if (!isDebuggable()) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 641400a50c89..0c6d7920d7f3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -22,6 +22,7 @@ import android.os.Handler
import android.os.Trace
import android.util.Log
import android.view.View
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.DejankUtils
@@ -54,7 +55,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
@@ -145,7 +145,7 @@ constructor(
TAG,
"PrimaryBouncerInteractor#show is being called before the " +
"primaryBouncerDelegate is set. Let's exit early so we don't " +
- "set the wrong primaryBouncer state."
+ "set the wrong primaryBouncer state.",
)
return false
}
@@ -197,7 +197,7 @@ constructor(
if (isFullyShowing()) {
SysUiStatsLog.write(
SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
- SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
+ SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN,
)
dismissCallbackRegistry.notifyDismissCancelled()
}
@@ -223,7 +223,7 @@ constructor(
fun setPanelExpansion(expansion: Float) {
val oldExpansion = repository.panelExpansionAmount.value
val expansionChanged = oldExpansion != expansion
- if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
+ if (!repository.isPrimaryBouncerStartingDisappearAnimation()) {
repository.setPanelExpansion(expansion)
}
@@ -272,7 +272,7 @@ constructor(
*/
fun setDismissAction(
onDismissAction: ActivityStarter.OnDismissAction?,
- cancelAction: Runnable?
+ cancelAction: Runnable?,
) {
repository.bouncerDismissActionModel =
if (onDismissAction != null && cancelAction != null) {
@@ -344,7 +344,7 @@ constructor(
fun isFullyShowing(): Boolean {
return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
- repository.primaryBouncerStartingDisappearAnimation.value == null
+ !repository.isPrimaryBouncerStartingDisappearAnimation()
}
/** Returns whether bouncer is scrimmed. */
@@ -361,7 +361,7 @@ constructor(
/** Return whether bouncer is animating away. */
fun isAnimatingAway(): Boolean {
- return repository.primaryBouncerStartingDisappearAnimation.value != null
+ return repository.isPrimaryBouncerStartingDisappearAnimation()
}
/** Return whether bouncer will dismiss with actions */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index d5c815d649c4..434a9ce58c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -170,8 +170,6 @@ object KeyguardBouncerViewBinder {
launch {
viewModel.startDisappearAnimation.collect {
- android.util.Log.i("KeyguardBouncerViewBinder",
- "viewModel.startDisappearAnimation: $it")
securityContainerController.startDisappearAnimation(it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
index 8814a1298e44..c0ad3b8c97c9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
@@ -20,15 +20,17 @@ import android.graphics.Bitmap
import android.net.Uri
import android.util.Log
import android.util.Size
-import com.android.systemui.res.R
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags.clipboardOverlayMultiuser
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
import java.io.IOException
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
@@ -36,8 +38,9 @@ class ClipboardImageLoader
@Inject
constructor(
private val context: Context,
+ private val userTracker: UserTracker,
@Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val mainScope: CoroutineScope
+ @Application private val mainScope: CoroutineScope,
) {
private val TAG: String = "ClipboardImageLoader"
@@ -46,7 +49,15 @@ constructor(
withContext(bgDispatcher) {
try {
val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
- context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
+ if (clipboardOverlayMultiuser()) {
+ userTracker.userContentResolver.loadThumbnail(
+ uri,
+ Size(size, size * 4),
+ null,
+ )
+ } else {
+ context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
+ }
} catch (e: IOException) {
Log.e(TAG, "Thumbnail loading failed!", e)
null
diff --git a/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
index 9af259a9b642..d10958a2ce4a 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
@@ -42,8 +42,8 @@ class ContextualEducationMetricsLogger @Inject constructor() {
}
SysUiStatsLog.write(
SysUiStatsLog.CONTEXTUAL_EDUCATION_TRIGGERED,
- statsGestureType,
statsEducationType,
+ statsGestureType,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 9596a540b63b..2412ad442e66 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -77,6 +77,8 @@ interface ContextualEducationRepository {
transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
)
+ suspend fun clear()
+
val keyboardShortcutTriggered: Flow<GestureType>
}
@@ -278,4 +280,8 @@ constructor(
preferences.remove(key)
}
}
+
+ override suspend fun clear() {
+ datastore.filterNotNull().first().edit { it.clear() }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
index 950a727aedae..0ab5a80e0044 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
@@ -25,6 +25,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
@@ -45,7 +46,10 @@ import com.android.systemui.res.R
fun ActionKeyTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
BackHandler(onBack = onBack)
val screenConfig = buildScreenConfig()
- var actionState: TutorialActionState by remember { mutableStateOf(NotStarted) }
+ var actionState: TutorialActionState by
+ rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
+ mutableStateOf(NotStarted)
+ }
val focusRequester = remember { FocusRequester() }
Box(
modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index 08e0a9d52faa..21afa40c441b 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -38,6 +38,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@@ -69,6 +71,31 @@ sealed interface TutorialActionState {
data class InProgressAfterError(val inProgress: InProgress) :
TutorialActionState, Progress by inProgress
+
+ companion object {
+ fun stateSaver(): Saver<TutorialActionState, Any> {
+ val classKey = "class"
+ val successAnimationKey = "animation"
+ return mapSaver(
+ save = {
+ buildMap {
+ put(classKey, it::class.java.name)
+ if (it is Finished) put(successAnimationKey, it.successAnimation)
+ }
+ },
+ restore = { map ->
+ when (map[classKey] as? String) {
+ NotStarted::class.java.name,
+ InProgress::class.java.name -> NotStarted
+ Error::class.java.name,
+ InProgressAfterError::class.java.name -> Error
+ Finished::class.java.name -> Finished(map[successAnimationKey]!! as Int)
+ else -> NotStarted
+ }
+ },
+ )
+ }
+ }
}
interface Progress {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index b0816ce608a0..dc0d7b689d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -131,10 +131,14 @@ private fun SuccessAnimation(
) {
val composition by
rememberLottieComposition(LottieCompositionSpec.RawRes(finishedState.successAnimation))
+ var animationFinished by rememberSaveable(key = "animationFinished") { mutableStateOf(false) }
val progress by animateLottieCompositionAsState(composition, iterations = 1)
+ if (progress == 1f) {
+ animationFinished = true
+ }
LottieAnimation(
composition = composition,
- progress = { progress },
+ progress = { if (animationFinished) 1f else progress },
dynamicProperties = animationProperties,
modifier = Modifier.fillMaxSize(),
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index ba31d08c9c1b..bf60c9a52417 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -105,6 +105,7 @@ import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
@@ -969,7 +970,7 @@ private fun CategoryItemTwoPane(
Text(
fontSize = 18.sp,
color = colors.textColor(selected).value,
- style = MaterialTheme.typography.titleSmall,
+ style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto),
text = label,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 647362873015..5baef915ea01 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3492,7 +3492,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
public void showSurfaceBehindKeyguard() {
mSurfaceBehindRemoteAnimationRequested = true;
- if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS && !KeyguardWmStateRefactor.isEnabled()) {
startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/BuildTextView.kt b/packages/SystemUI/src/com/android/systemui/qs/BuildTextView.kt
new file mode 100644
index 000000000000..1f864e9d7b8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/BuildTextView.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.qs
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.accessibility.AccessibilityNodeInfo
+import com.android.systemui.res.R
+import com.android.systemui.util.DelayableMarqueeTextView
+
+class BuildTextView
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0,
+) : DelayableMarqueeTextView(context, attrs, defStyleAttr, defStyleRes) {
+
+ override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
+ super.onInitializeAccessibilityNodeInfo(info)
+ // Clear selected state so it's not announced by accessibility, but we can still marquee.
+ info?.isSelected = false
+ info?.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ resources.getString(R.string.copy_to_clipboard_a11y_action),
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
index e1ec338cec6f..a6edb586776b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -27,7 +27,6 @@ import com.android.systemui.qs.pipeline.domain.autoaddable.DataSaverAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.DeviceControlsAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.HotspotAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.NightDisplayAutoAddable
-import com.android.systemui.qs.pipeline.domain.autoaddable.ReduceBrightColorsAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.WalletAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.WorkTileAutoAddable
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
@@ -45,11 +44,11 @@ interface BaseAutoAddableModule {
@ElementsIntoSet
fun providesAutoAddableSetting(
@Main resources: Resources,
- autoAddableSettingFactory: AutoAddableSetting.Factory
+ autoAddableSettingFactory: AutoAddableSetting.Factory,
): Set<AutoAddable> {
return AutoAddableSettingList.parseSettingsResource(
resources,
- autoAddableSettingFactory
+ autoAddableSettingFactory,
)
.toSet()
}
@@ -75,10 +74,6 @@ interface BaseAutoAddableModule {
@Binds @IntoSet fun bindNightDisplayAutoAddable(impl: NightDisplayAutoAddable): AutoAddable
- @Binds
- @IntoSet
- fun bindReduceBrightColorsAutoAddable(impl: ReduceBrightColorsAutoAddable): AutoAddable
-
@Binds @IntoSet fun bindWalletAutoAddable(impl: WalletAutoAddable): AutoAddable
@Binds @IntoSet fun bindWorkModeAutoAddable(impl: WorkTileAutoAddable): AutoAddable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
index a0c9737de0ee..791339c75eeb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.view.accessibility.Flags
import com.android.internal.accessibility.AccessibilityShortcutController
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -33,31 +32,27 @@ object A11yShortcutAutoAddableList {
* accessibility features with shortcut options
*/
fun getA11yShortcutAutoAddables(factory: A11yShortcutAutoAddable.Factory): Set<AutoAddable> {
- return if (Flags.a11yQsShortcut()) {
- setOf(
- factory.create(
- TileSpec.create(ColorCorrectionTile.TILE_SPEC),
- AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
- ),
- factory.create(
- TileSpec.create(ColorInversionTile.TILE_SPEC),
- AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
- ),
- factory.create(
- TileSpec.create(OneHandedModeTile.TILE_SPEC),
- AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
- ),
- factory.create(
- TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
- AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
- ),
- factory.create(
- TileSpec.create(HearingDevicesTile.TILE_SPEC),
- AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME
- )
- )
- } else {
- emptySet()
- }
+ return setOf(
+ factory.create(
+ TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME,
+ ),
+ factory.create(
+ TileSpec.create(ColorInversionTile.TILE_SPEC),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME,
+ ),
+ factory.create(
+ TileSpec.create(OneHandedModeTile.TILE_SPEC),
+ AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME,
+ ),
+ factory.create(
+ TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+ AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
+ ),
+ factory.create(
+ TileSpec.create(HearingDevicesTile.TILE_SPEC),
+ AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME,
+ ),
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
deleted file mode 100644
index bde682074780..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.pipeline.domain.autoaddable
-
-import android.view.accessibility.Flags
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.ReduceBrightColorsController
-import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
-import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
-import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.ReduceBrightColorsTile
-import javax.inject.Inject
-import javax.inject.Named
-import kotlinx.coroutines.channels.ProducerScope
-
-/**
- * [AutoAddable] for [ReduceBrightColorsTile.TILE_SPEC].
- *
- * It will send a signal to add the tile when reduce bright colors is enabled.
- */
-@SysUISingleton
-class ReduceBrightColorsAutoAddable
-@Inject
-constructor(
- controller: ReduceBrightColorsController,
- @Named(RBC_AVAILABLE) private var available: Boolean,
-) :
- CallbackControllerAutoAddable<
- ReduceBrightColorsController.Listener,
- ReduceBrightColorsController,
- >(controller) {
-
- override val spec: TileSpec
- get() = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
-
- override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
- return object : ReduceBrightColorsController.Listener {
- override fun onActivated(activated: Boolean) {
- if (activated && available) {
- sendAdd()
- }
- }
- }
- }
-
- override val autoAddTracking
- get() =
- if (Flags.a11yQsShortcut()) {
- AutoAddTracking.Disabled
- } else if (available) {
- super.autoAddTracking
- } else {
- AutoAddTracking.Disabled
- }
-
- override val description = "ReduceBrightColorsAutoAddable ($autoAddTracking)"
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt
index 56c3e0e648bf..481d1e3c28bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.pipeline.domain.interactor
import android.content.Context
-import android.view.accessibility.Flags
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -30,7 +30,6 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Observe the tiles in the QS Panel and perform accessibility related actions */
@SysUISingleton
@@ -48,10 +47,7 @@ constructor(
if (!initialized.compareAndSet(/* expectedValue= */ false, /* newValue= */ true)) {
return
}
-
- if (Flags.a11yQsShortcut()) {
- startObservingTiles(currentTilesInteractor)
- }
+ startObservingTiles(currentTilesInteractor)
}
private fun startObservingTiles(currentTilesInteractor: CurrentTilesInteractor) {
@@ -63,14 +59,11 @@ constructor(
.collectLatest {
a11yQsShortcutsRepository.notifyAccessibilityManagerTilesChanged(
it.userContext,
- it.currentTileSpecs
+ it.currentTileSpecs,
)
}
}
}
- private data class Data(
- val currentTileSpecs: List<TileSpec>,
- val userContext: Context,
- )
+ private data class Data(val currentTileSpecs: List<TileSpec>, val userContext: Context)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index c0c0aea073f1..465d08b36ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -17,13 +17,14 @@
package com.android.systemui.qs.ui.viewmodel
import androidx.compose.runtime.getValue
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
@@ -44,6 +45,7 @@ class QuickSettingsShadeOverlayContentViewModel
constructor(
val shadeInteractor: ShadeInteractor,
val sceneInteractor: SceneInteractor,
+ val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
) : ExclusiveActivatable() {
@@ -92,6 +94,11 @@ constructor(
awaitCancellation()
}
+ /** Notifies that the bounds of the QuickSettings panel have changed. */
+ fun onPanelShapeChanged(shape: ShadeScrimShape?) {
+ notificationStackAppearanceInteractor.setQsPanelShape(shape)
+ }
+
fun onScrimClicked() {
shadeInteractor.collapseQuickSettingsShade(loggingReason = "shade scrim clicked")
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
index e358dcec8b10..ec9bba7c1f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
@@ -95,7 +95,7 @@ constructor(
*/
@Synchronized
fun onShadeDisplayChanging(displayId: Int) {
- previousJob?.cancel(CancellationException("New shade move in progress"))
+ previousJob?.cancel(CancellationException("New shade move in progress to $displayId"))
previousJob = bgScope.launch { onShadeDisplayChangingAsync(displayId) }
}
@@ -109,8 +109,8 @@ constructor(
val reason =
when (e) {
is CancellationException ->
- "Shade move cancelled as a new move is being done " +
- "before the previous one finished."
+ "Shade move to $displayId cancelled as a new move is being done " +
+ "before the previous one finished. Message: ${e.message}"
else -> "Shade move cancelled."
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
index 17b5e5b584b4..d53f9f7ec595 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.display
+import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -33,11 +34,33 @@ interface ShadeDisplayPolicy {
val displayId: StateFlow<Int>
}
+/** Return the latest element the user intended to expand in the shade (notifications or QS). */
+interface ShadeExpansionIntent {
+ /**
+ * Returns the latest element the user intended to expand in the shade (notifications or QS).
+ *
+ * When the shade moves to a different display (e.g., due to a touch on the status bar of an
+ * external display), it's first collapsed and then re-expanded on the target display.
+ *
+ * If the user was trying to open a specific element (QS or notifications) when the shade was on
+ * the original display, that intention might be lost during the collapse/re-expand transition.
+ * This is used to preserve the user's intention, ensuring the correct element is expanded on
+ * the target display.
+ *
+ * Note that the expansion intent is kept for a very short amount of time (ideally, just a bit
+ * above the time it takes for the shade to collapse)
+ */
+ fun consumeExpansionIntent(): ShadeElement?
+}
+
@Module
interface ShadeDisplayPolicyModule {
@Binds fun provideDefaultPolicy(impl: StatusBarTouchShadeDisplayPolicy): ShadeDisplayPolicy
+ @Binds
+ fun provideShadeExpansionIntent(impl: StatusBarTouchShadeDisplayPolicy): ShadeExpansionIntent
+
@IntoSet
@Binds
fun provideDefaultDisplayPolicyToSet(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
index 30b086f03d66..91020aa7bdb0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
@@ -18,16 +18,25 @@ package com.android.systemui.shade.display
import android.util.Log
import android.view.Display
+import android.view.MotionEvent
import com.android.app.tracing.coroutines.launchTraced
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.keyguard.data.repository.KeyguardRepository
import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked
+import com.android.systemui.shade.domain.interactor.NotificationShadeElement
+import com.android.systemui.shade.domain.interactor.QSShadeElement
+import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Lazy
+import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -49,14 +58,20 @@ class StatusBarTouchShadeDisplayPolicy
constructor(
displayRepository: DisplayRepository,
keyguardRepository: KeyguardRepository,
- @Background val backgroundScope: CoroutineScope,
- @ShadeOnDefaultDisplayWhenLocked val shadeOnDefaultDisplayWhenLocked: Boolean,
-) : ShadeDisplayPolicy {
+ @Background private val backgroundScope: CoroutineScope,
+ @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean,
+ private val shadeInteractor: Lazy<ShadeInteractor>,
+ private val qsShadeElement: Lazy<QSShadeElement>,
+ private val notificationElement: Lazy<NotificationShadeElement>,
+) : ShadeDisplayPolicy, ShadeExpansionIntent {
override val name: String = "status_bar_latest_touch"
private val currentDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
private val availableDisplayIds: StateFlow<Set<Int>> = displayRepository.displayIds
+ private var latestIntent = AtomicReference<ShadeElement?>()
+ private var timeoutJob: Job? = null
+
override val displayId: StateFlow<Int> =
if (shadeOnDefaultDisplayWhenLocked) {
keyguardRepository.isKeyguardShowing
@@ -75,8 +90,29 @@ constructor(
private var removalListener: Job? = null
/** Called when the status bar on the given display is touched. */
- fun onStatusBarTouched(statusBarDisplayId: Int) {
+ fun onStatusBarTouched(event: MotionEvent, statusBarWidth: Int) {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ updateShadeDisplayIfNeeded(event)
+ updateExpansionIntent(event, statusBarWidth)
+ }
+
+ override fun consumeExpansionIntent(): ShadeElement? {
+ return latestIntent.getAndSet(null)
+ }
+
+ private fun updateExpansionIntent(event: MotionEvent, statusBarWidth: Int) {
+ val element = classifyStatusBarEvent(event, statusBarWidth)
+ latestIntent.set(element)
+ timeoutJob?.cancel()
+ timeoutJob =
+ backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#intentTimeout") {
+ delay(EXPANSION_INTENT_EXPIRY)
+ latestIntent.set(null)
+ }
+ }
+
+ private fun updateShadeDisplayIfNeeded(event: MotionEvent) {
+ val statusBarDisplayId = event.displayId
if (statusBarDisplayId !in availableDisplayIds.value) {
Log.e(TAG, "Got touch on unknown display $statusBarDisplayId")
return
@@ -90,6 +126,17 @@ constructor(
}
}
+ private fun classifyStatusBarEvent(
+ motionEvent: MotionEvent,
+ statusbarWidth: Int,
+ ): ShadeElement {
+ val xPercentage = motionEvent.x / statusbarWidth
+ val threshold = shadeInteractor.get().getTopEdgeSplitFraction()
+ return if (xPercentage < threshold) {
+ notificationElement.get()
+ } else qsShadeElement.get()
+ }
+
private fun monitorDisplayRemovals(): Job {
return backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#monitorDisplayRemovals") {
currentDisplayId.subscriptionCount
@@ -112,5 +159,6 @@ constructor(
private companion object {
const val TAG = "StatusBarTouchDisplayPolicy"
+ val EXPANSION_INTENT_EXPIRY = 2.seconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 691a383cb338..f67d33122063 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -30,6 +30,7 @@ import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker
import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo
import com.android.systemui.shade.ShadeTraceLogger.traceReparenting
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.display.ShadeExpansionIntent
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.window.flags.Flags
import java.util.Optional
@@ -49,6 +50,7 @@ constructor(
@Main private val mainThreadContext: CoroutineContext,
private val shadeDisplayChangeLatencyTracker: ShadeDisplayChangeLatencyTracker,
shadeExpandedInteractor: Optional<ShadeExpandedStateInteractor>,
+ private val shadeExpansionIntent: ShadeExpansionIntent,
) : CoreStartable {
private val shadeExpandedInteractor =
@@ -90,10 +92,7 @@ constructor(
withContext(mainThreadContext) {
traceReparenting {
shadeDisplayChangeLatencyTracker.onShadeDisplayChanging(destinationId)
- val expandedElement = shadeExpandedInteractor.currentlyExpandedElement.value
- expandedElement?.collapse(reason = "Shade window move")
- reparentToDisplayId(id = destinationId)
- expandedElement?.expand(reason = "Shade window move")
+ collapseAndExpandShadeIfNeeded { reparentToDisplayId(id = destinationId) }
checkContextDisplayMatchesExpected(destinationId)
}
}
@@ -106,6 +105,18 @@ constructor(
}
}
+ private suspend fun collapseAndExpandShadeIfNeeded(wrapped: () -> Unit) {
+ val previouslyExpandedElement = shadeExpandedInteractor.currentlyExpandedElement.value
+ previouslyExpandedElement?.collapse(reason = COLLAPSE_EXPAND_REASON)
+
+ wrapped()
+
+ // If the user was trying to expand a specific shade element, let's make sure to expand
+ // that one. Otherwise, we can just re-expand the previous expanded element.
+ shadeExpansionIntent.consumeExpansionIntent()?.expand(COLLAPSE_EXPAND_REASON)
+ ?: previouslyExpandedElement?.expand(reason = COLLAPSE_EXPAND_REASON)
+ }
+
private fun checkContextDisplayMatchesExpected(destinationId: Int) {
if (shadeContext.displayId != destinationId) {
Log.wtf(
@@ -125,5 +136,6 @@ constructor(
private companion object {
const val TAG = "ShadeDisplaysInteractor"
+ const val COLLAPSE_EXPAND_REASON = "Shade window move"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
index dd3abeec5a72..aba5a6bceb10 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.domain.interactor
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -24,6 +25,8 @@ import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.util.kotlin.Utils.Companion.combineState
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,7 +34,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
-import kotlinx.coroutines.withTimeout
+import kotlinx.coroutines.withTimeoutOrNull
/**
* Wrapper around [ShadeInteractor] to facilitate expansion and collapse of Notifications and quick
@@ -47,7 +50,7 @@ interface ShadeExpandedStateInteractor {
val currentlyExpandedElement: StateFlow<ShadeElement?>
/** An element from the shade window that can be expanded or collapsed. */
- abstract class ShadeElement {
+ sealed class ShadeElement {
/** Expands the shade element, returning when the expansion is done */
abstract suspend fun expand(reason: String)
@@ -56,17 +59,18 @@ interface ShadeExpandedStateInteractor {
}
}
+private val EXPAND_COLLAPSE_TIMEOUT: Duration = 1.seconds
+
@SysUISingleton
class ShadeExpandedStateInteractorImpl
@Inject
constructor(
- private val shadeInteractor: ShadeInteractor,
+ shadeInteractor: ShadeInteractor,
@Background private val bgScope: CoroutineScope,
+ private val notificationElement: NotificationShadeElement,
+ private val qsElement: QSShadeElement,
) : ShadeExpandedStateInteractor {
- private val notificationElement = NotificationElement()
- private val qsElement = QSElement()
-
override val currentlyExpandedElement: StateFlow<ShadeElement?> =
if (SceneContainerFlag.isEnabled) {
combineState(
@@ -84,35 +88,54 @@ constructor(
} else {
MutableStateFlow(null)
}
+}
- inner class NotificationElement : ShadeElement() {
- override suspend fun expand(reason: String) {
- shadeInteractor.expandNotificationsShade(reason)
- shadeInteractor.shadeExpansion.waitUntil(1f)
+private suspend fun StateFlow<Float>.waitUntil(f: Float, coroutineContext: CoroutineContext) {
+ // it's important to not do this in the main thread otherwise it will block any rendering.
+ withContext(coroutineContext) {
+ withTimeoutOrNull(EXPAND_COLLAPSE_TIMEOUT) {
+ traceWaitForExpansion(expansion = f) { first { it == f } }
}
+ ?: Log.e(
+ "ShadeExpStateInteractor",
+ "Timed out after ${EXPAND_COLLAPSE_TIMEOUT.inWholeMilliseconds}ms while waiting " +
+ "for expansion to match $f. Current one: $value",
+ )
+ }
+}
- override suspend fun collapse(reason: String) {
- shadeInteractor.collapseNotificationsShade(reason)
- shadeInteractor.shadeExpansion.waitUntil(0f)
- }
+@SysUISingleton
+class NotificationShadeElement
+@Inject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ @Background private val bgContext: CoroutineContext,
+) : ShadeElement() {
+ override suspend fun expand(reason: String) {
+ shadeInteractor.expandNotificationsShade(reason)
+ shadeInteractor.shadeExpansion.waitUntil(1f, bgContext)
}
- inner class QSElement : ShadeElement() {
- override suspend fun expand(reason: String) {
- shadeInteractor.expandQuickSettingsShade(reason)
- shadeInteractor.qsExpansion.waitUntil(1f)
- }
+ override suspend fun collapse(reason: String) {
+ shadeInteractor.collapseNotificationsShade(reason)
+ shadeInteractor.shadeExpansion.waitUntil(0f, bgContext)
+ }
+}
- override suspend fun collapse(reason: String) {
- shadeInteractor.collapseQuickSettingsShade(reason)
- shadeInteractor.qsExpansion.waitUntil(0f)
- }
+@SysUISingleton
+class QSShadeElement
+@Inject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ @Background private val bgContext: CoroutineContext,
+) : ShadeElement() {
+ override suspend fun expand(reason: String) {
+ shadeInteractor.expandQuickSettingsShade(reason)
+ shadeInteractor.qsExpansion.waitUntil(1f, bgContext)
}
- private suspend fun StateFlow<Float>.waitUntil(f: Float) {
- // it's important to not do this in the main thread otherwise it will block any rendering.
- withContext(bgScope.coroutineContext) {
- withTimeout(1.seconds) { traceWaitForExpansion(expansion = f) { first { it == f } } }
- }
+ override suspend fun collapse(reason: String) {
+ shadeInteractor.collapseQuickSettingsShade(reason)
+ shadeInteractor.qsExpansion.waitUntil(0f, bgContext)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 5ef5a7d2139c..af51c49204bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -284,6 +284,7 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
| WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
| WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
lp.setTitle("ImmersiveModeConfirmation");
+ lp.accessibilityTitle = mSysUiContext.getString(R.string.immersive_cling_title);
lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
lp.token = getWindowToken();
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
index 54a18f764406..06474b01287d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.core
-import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
+import com.android.systemui.shared.Flags
/** Helper for reading or using the status bar connected displays flag state. */
@Suppress("NOTHING_TO_INLINE")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
index 057213fa4b18..3c30f3cbec85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
@@ -25,6 +25,9 @@ object StatusBarRootModernization {
/** Aconfig flag for removing the fragment */
const val FLAG_NAME = Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION
+ /** Shows a "compose->bar" text in the status bar for debug purposes */
+ const val SHOW_DISAMBIGUATION = false
+
/** A token used for dependency declaration */
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 70e27a981b49..7b3a4710b69c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -504,6 +504,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
CharSequence redactedMessage = systemUiContext.getString(
R.string.redacted_notification_single_line_text
);
+ redacted.setWhen(original.getWhen());
if (originalStyle instanceof MessagingStyle oldStyle) {
MessagingStyle newStyle = new MessagingStyle(oldStyle.getUser());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index 98d704c75d33..f3ee34f45a50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row.icon
import android.annotation.WorkerThread
import android.app.ActivityManager
import android.app.Flags
+import android.app.Flags.notificationsRedesignThemedAppIcons
import android.content.Context
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.Color
@@ -29,6 +30,8 @@ import android.util.Log
import com.android.internal.R
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.mono.MonoIconThemeController
import com.android.launcher3.util.UserIconInfo
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
@@ -55,6 +58,7 @@ interface AppIconProvider {
packageName: String,
context: Context,
withWorkProfileBadge: Boolean = false,
+ themed: Boolean = true,
): Drawable
/**
@@ -74,6 +78,17 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
dumpManager.registerNormalDumpable(TAG, this)
}
+ private class NotificationIcons(context: Context?, fillResIconDpi: Int, iconBitmapSize: Int) :
+ BaseIconFactory(context, fillResIconDpi, iconBitmapSize) {
+
+ init {
+ if (notificationsRedesignThemedAppIcons()) {
+ // Initialize the controller so that we can support themed icons.
+ mThemeController = MonoIconThemeController()
+ }
+ }
+ }
+
private val iconFactory: BaseIconFactory
get() {
val isLowRam = ActivityManager.isLowRamDeviceStatic()
@@ -83,7 +98,7 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
if (isLowRam) R.dimen.notification_small_icon_size_low_ram
else R.dimen.notification_small_icon_size
)
- return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize)
+ return NotificationIcons(sysuiContext, res.configuration.densityDpi, iconSize)
}
private val cache = NotifCollectionCache<Drawable>()
@@ -92,12 +107,15 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
packageName: String,
context: Context,
withWorkProfileBadge: Boolean,
+ themed: Boolean,
): Drawable {
// Add a suffix to distinguish the app installed on the work profile, since the icon will
// be different.
val key = packageName + if (withWorkProfileBadge) WORK_SUFFIX else ""
- return cache.getOrFetch(key) { fetchAppIcon(packageName, context, withWorkProfileBadge) }
+ return cache.getOrFetch(key) {
+ fetchAppIcon(packageName, context, withWorkProfileBadge, themed)
+ }
}
@WorkerThread
@@ -105,6 +123,7 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
packageName: String,
context: Context,
withWorkProfileBadge: Boolean,
+ themed: Boolean,
): Drawable {
val pm = context.packageManager
val icon = pm.getApplicationInfo(packageName, 0).loadUnbadgedIcon(pm)
@@ -113,13 +132,14 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
IconOptions().apply {
setUser(userIconInfo(context, withWorkProfileBadge))
setBitmapGenerationMode(BaseIconFactory.MODE_HARDWARE)
- // This color is not used since we're not showing the themed icons. We're just
- // setting it so that the icon factory doesn't try to extract colors from our bitmap
- // (since it won't work, given it's a hardware bitmap).
+ // This color will not be used, but we're just setting it so that the icon factory
+ // doesn't try to extract colors from our bitmap (since it won't work, given it's a
+ // hardware bitmap).
setExtractedColor(Color.BLUE)
}
val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
- return badgedIcon.newIcon(sysuiContext)
+ val creationFlags = if (themed) BitmapInfo.FLAG_THEMED else 0
+ return badgedIcon.newIcon(sysuiContext, creationFlags)
}
private fun userIconInfo(context: Context, withWorkProfileBadge: Boolean): UserIconInfo {
@@ -165,6 +185,7 @@ class NoOpIconProvider : AppIconProvider {
packageName: String,
context: Context,
withWorkProfileBadge: Boolean,
+ themed: Boolean,
): Drawable {
Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
return ColorDrawable(Color.WHITE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
index 64fdf6fc2708..bb4aa86fc6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row.icon
+import android.app.Flags.notificationsRedesignThemedAppIcons
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
@@ -74,6 +75,7 @@ constructor(
sbn.packageName,
context,
withWorkProfileBadge,
+ themed = notificationsRedesignThemedAppIcons(),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7b55e83a0a99..a8a1318664f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -512,23 +512,27 @@ public class NotificationStackScrollLayout
*/
private final Path mRoundedClipPath = new Path();
+ /** The clip path defining where we are NOT allowed to draw. */
+ private final Path mNegativeRoundedClipPath = new Path();
+
/**
* The clip Path used to clip the launching notification. This may be different
* from the normal path, as the views launch animation could start clipped.
*/
private final Path mLaunchedNotificationClipPath = new Path();
- /**
- * Should we use rounded rect clipping right now
- */
+ /** Should we use rounded rect clipping right now */
private boolean mShouldUseRoundedRectClipping = false;
+ /** Should we set an out path for the drawing canvas */
+ private boolean mShouldUseNegativeRoundedRectClipping = false;
+
private int mRoundedRectClippingLeft;
private int mRoundedRectClippingTop;
private int mRoundedRectClippingBottom;
private int mRoundedRectClippingRight;
private int mRoundedRectClippingYTranslation;
- private final float[] mBgCornerRadii = new float[8];
+ private final float[] mRoundedClipCornerRadii = new float[8];
/**
* Whether stackY should be animated in case the view is getting shorter than the scroll
@@ -3864,7 +3868,7 @@ public class NotificationStackScrollLayout
if (!SceneContainerFlag.isEnabled()) {
return !isInsideQsHeader(ev);
}
- ShadeScrimShape shape = mScrollViewFields.getScrimClippingShape();
+ ShadeScrimShape shape = mScrollViewFields.getClippingShape();
if (shape == null) {
return true; // When there is no scrim, consider this event scrollable.
}
@@ -5390,7 +5394,8 @@ public class NotificationStackScrollLayout
println(pw, "pulsing", mPulsing);
println(pw, "expanded", mIsExpanded);
println(pw, "headsUpPinned", mInHeadsUpPinnedMode);
- println(pw, "qsClipping", mShouldUseRoundedRectClipping);
+ println(pw, "roundedRectClipping", mShouldUseRoundedRectClipping);
+ println(pw, "negativeRoundedRectClipping", mShouldUseNegativeRoundedRectClipping);
println(pw, "qsClipDismiss", mDismissUsingRowTranslationX);
println(pw, "visibility", visibilityString(getVisibility()));
println(pw, "alpha", getAlpha());
@@ -5469,8 +5474,8 @@ public class NotificationStackScrollLayout
pw.append(" r=").print(mRoundedRectClippingRight);
pw.append(" b=").print(mRoundedRectClippingBottom);
pw.append(" +y=").print(mRoundedRectClippingYTranslation);
- pw.append("} topRadius=").print(mBgCornerRadii[0]);
- pw.append(" bottomRadius=").println(mBgCornerRadii[4]);
+ pw.append("} topRadius=").print(mRoundedClipCornerRadii[0]);
+ pw.append(" bottomRadius=").println(mRoundedClipCornerRadii[4]);
}
public boolean isFullyHidden() {
@@ -5917,14 +5922,12 @@ public class NotificationStackScrollLayout
mScrollListener = listener;
}
- /**
- * Set rounded rect clipping bounds on this view.
- */
+ /** Sets rounded rect where the view is allowed to draw. */
@Override
- public void setScrimClippingShape(@Nullable ShadeScrimShape shape) {
+ public void setClippingShape(@Nullable ShadeScrimShape shape) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
- if (Objects.equals(mScrollViewFields.getScrimClippingShape(), shape)) return;
- mScrollViewFields.setScrimClippingShape(shape);
+ if (Objects.equals(mScrollViewFields.getClippingShape(), shape)) return;
+ mScrollViewFields.setClippingShape(shape);
mShouldUseRoundedRectClipping = shape != null;
mRoundedClipPath.reset();
if (shape != null) {
@@ -5933,17 +5936,36 @@ public class NotificationStackScrollLayout
mRoundedRectClippingTop = (int) bounds.getTop();
mRoundedRectClippingRight = (int) bounds.getRight();
mRoundedRectClippingBottom = (int) bounds.getBottom();
- mBgCornerRadii[0] = shape.getTopRadius();
- mBgCornerRadii[1] = shape.getTopRadius();
- mBgCornerRadii[2] = shape.getTopRadius();
- mBgCornerRadii[3] = shape.getTopRadius();
- mBgCornerRadii[4] = shape.getBottomRadius();
- mBgCornerRadii[5] = shape.getBottomRadius();
- mBgCornerRadii[6] = shape.getBottomRadius();
- mBgCornerRadii[7] = shape.getBottomRadius();
+ mRoundedClipCornerRadii[0] = shape.getTopRadius();
+ mRoundedClipCornerRadii[1] = shape.getTopRadius();
+ mRoundedClipCornerRadii[2] = shape.getTopRadius();
+ mRoundedClipCornerRadii[3] = shape.getTopRadius();
+ mRoundedClipCornerRadii[4] = shape.getBottomRadius();
+ mRoundedClipCornerRadii[5] = shape.getBottomRadius();
+ mRoundedClipCornerRadii[6] = shape.getBottomRadius();
+ mRoundedClipCornerRadii[7] = shape.getBottomRadius();
mRoundedClipPath.addRoundRect(
bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(),
- mBgCornerRadii, Path.Direction.CW);
+ mRoundedClipCornerRadii, Path.Direction.CW);
+ }
+ invalidate();
+ }
+
+ @Override
+ public void setNegativeClippingShape(@Nullable ShadeScrimShape shape) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (Objects.equals(mScrollViewFields.getNegativeClippingShape(), shape)) return;
+
+ mScrollViewFields.setNegativeClippingShape(shape);
+ mShouldUseNegativeRoundedRectClipping = shape != null;
+ mNegativeRoundedClipPath.reset();
+ if (shape != null) {
+ ShadeScrimBounds bounds = shape.getBounds();
+ float bottomRadius = shape.getBottomRadius();
+ mNegativeRoundedClipPath.addRoundRect(
+ bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(),
+ new float[]{0, 0, 0, 0, bottomRadius, bottomRadius, bottomRadius, bottomRadius},
+ Path.Direction.CW);
}
invalidate();
}
@@ -5956,21 +5978,22 @@ public class NotificationStackScrollLayout
SceneContainerFlag.assertInLegacyMode();
if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
&& mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
- && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
+ && mRoundedClipCornerRadii[0] == topRadius
+ && mRoundedClipCornerRadii[5] == bottomRadius) {
return;
}
mRoundedRectClippingLeft = left;
mRoundedRectClippingTop = top;
mRoundedRectClippingBottom = bottom;
mRoundedRectClippingRight = right;
- mBgCornerRadii[0] = topRadius;
- mBgCornerRadii[1] = topRadius;
- mBgCornerRadii[2] = topRadius;
- mBgCornerRadii[3] = topRadius;
- mBgCornerRadii[4] = bottomRadius;
- mBgCornerRadii[5] = bottomRadius;
- mBgCornerRadii[6] = bottomRadius;
- mBgCornerRadii[7] = bottomRadius;
+ mRoundedClipCornerRadii[0] = topRadius;
+ mRoundedClipCornerRadii[1] = topRadius;
+ mRoundedClipCornerRadii[2] = topRadius;
+ mRoundedClipCornerRadii[3] = topRadius;
+ mRoundedClipCornerRadii[4] = bottomRadius;
+ mRoundedClipCornerRadii[5] = bottomRadius;
+ mRoundedClipCornerRadii[6] = bottomRadius;
+ mRoundedClipCornerRadii[7] = bottomRadius;
updateRoundedClipPath();
}
@@ -5992,7 +6015,7 @@ public class NotificationStackScrollLayout
mRoundedRectClippingTop + mRoundedRectClippingYTranslation,
mRoundedRectClippingRight,
mRoundedRectClippingBottom + mRoundedRectClippingYTranslation,
- mBgCornerRadii, Path.Direction.CW);
+ mRoundedClipCornerRadii, Path.Direction.CW);
if (mShouldUseRoundedRectClipping) {
invalidate();
}
@@ -6116,35 +6139,49 @@ public class NotificationStackScrollLayout
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- if (mShouldUseRoundedRectClipping && !mLaunchingNotification) {
+ protected void dispatchDraw(@NonNull Canvas canvas) {
+ if (!mLaunchingNotification) {
// When launching notifications, we're clipping the children individually instead of in
// dispatchDraw
- // Let's clip rounded.
- canvas.clipPath(mRoundedClipPath);
+ if (mShouldUseRoundedRectClipping) {
+ // Let's clip rounded.
+ canvas.clipPath(mRoundedClipPath);
+ }
+ if (mShouldUseNegativeRoundedRectClipping) {
+ // subtract the negative path if it is defined
+ canvas.clipOutPath(mNegativeRoundedClipPath);
+ }
}
super.dispatchDraw(canvas);
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (mShouldUseRoundedRectClipping && mLaunchingNotification) {
+ boolean shouldUseClipping =
+ mShouldUseRoundedRectClipping || mShouldUseNegativeRoundedRectClipping;
+ if (mLaunchingNotification && shouldUseClipping) {
// Let's clip children individually during notification launch
canvas.save();
ExpandableView expandableView = (ExpandableView) child;
Path clipPath;
+ Path clipOutPath;
if (expandableView.isExpandAnimationRunning()
|| ((ExpandableView) child).hasExpandingChild()) {
// When launching the notification, it is not clipped by this layout, but by the
// view itself. This is because the view is Translating in Z, where this clipPath
// wouldn't apply.
clipPath = null;
+ clipOutPath = null;
} else {
clipPath = mRoundedClipPath;
+ clipOutPath = mNegativeRoundedClipPath;
}
- if (clipPath != null) {
+ if (mShouldUseRoundedRectClipping && clipPath != null) {
canvas.clipPath(clipPath);
}
+ if (mShouldUseNegativeRoundedRectClipping && clipOutPath != null) {
+ canvas.clipOutPath(clipOutPath);
+ }
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index c717e3b229be..dc0fae80d041 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1207,7 +1207,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mView.getEmptyShadeViewHeight();
}
- /** Set the max alpha for keyguard */
+ /**
+ * Controls fading out Notifications during animations over the LockScreen, such opening or
+ * closing the shade. Note that we don't restrict Notification alpha in certain cases,
+ * like when the Shade is opened from a HUN.
+ */
public void setMaxAlphaForKeyguard(float alpha, String source) {
mMaxAlphaForKeyguard = alpha;
mMaxAlphaForKeyguardSource = source;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index fa20e43d0534..2593ef07751b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -33,7 +33,10 @@ import java.util.function.Consumer
*/
class ScrollViewFields {
/** Used to produce the clipping path */
- var scrimClippingShape: ShadeScrimShape? = null
+ var clippingShape: ShadeScrimShape? = null
+
+ /** Used to produce a negative clipping path */
+ var negativeClippingShape: ShadeScrimShape? = null
/** Scroll state of the notification shade. */
var scrollState: ShadeScrollState = ShadeScrollState()
@@ -97,7 +100,8 @@ class ScrollViewFields {
fun dump(pw: IndentingPrintWriter) {
pw.printSection("StackViewStates") {
- pw.println("scrimClippingShape", scrimClippingShape)
+ pw.println("scrimClippingShape", clippingShape)
+ pw.println("negativeClippingShape", negativeClippingShape)
pw.println("scrollState", scrollState)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index 5ec4c8988697..1d196c2fc079 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack.data.repository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState
import java.util.function.Consumer
import javax.inject.Inject
@@ -42,7 +43,15 @@ class NotificationPlaceholderRepository @Inject constructor() {
*
* When `null`, clipping should not be applied to notifications.
*/
- val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
+ val notificationShadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
+
+ /**
+ * The shape of the QuickSettings overlay panel. Used to clip Notification content when the QS
+ * covers it.
+ *
+ * When `null`, it doesn't affect notification clipping.
+ */
+ val qsPanelShape = MutableStateFlow<ShadeScrimShape?>(null)
/** height made available to the notifications in the size-constrained mode of lock screen. */
val constrainedAvailableSpace = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index d4dd1d4b9e0b..406a0dbb120f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.data.repository.Notific
import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState
import java.util.function.Consumer
import javax.inject.Inject
@@ -47,8 +48,11 @@ constructor(
shadeInteractor: ShadeInteractor,
) {
/** The bounds of the notification stack in the current scene. */
- val shadeScrimBounds: StateFlow<ShadeScrimBounds?> =
- placeholderRepository.shadeScrimBounds.asStateFlow()
+ val notificationShadeScrimBounds: StateFlow<ShadeScrimBounds?> =
+ placeholderRepository.notificationShadeScrimBounds.asStateFlow()
+
+ /** The shape of the QuickSettingsShadeOverlay panel */
+ val qsPanelShape: StateFlow<ShadeScrimShape?> = placeholderRepository.qsPanelShape.asStateFlow()
/**
* Whether the stack is expanding from GONE-with-HUN to SHADE
@@ -119,9 +123,15 @@ constructor(
}
/** Sets the position of the notification stack in the current scene. */
- fun setShadeScrimBounds(bounds: ShadeScrimBounds?) {
- check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
- placeholderRepository.shadeScrimBounds.value = bounds
+ fun setNotificationShadeScrimBounds(bounds: ShadeScrimBounds?) {
+ checkValidBounds(bounds)
+ placeholderRepository.notificationShadeScrimBounds.value = bounds
+ }
+
+ /** Sets the bounds of the QuickSettings overlay panel */
+ fun setQsPanelShape(shape: ShadeScrimShape?) {
+ checkValidBounds(shape?.bounds)
+ placeholderRepository.qsPanelShape.value = shape
}
/** Updates the current scroll state of the notification shade. */
@@ -156,4 +166,8 @@ constructor(
fun setConstrainedAvailableSpace(height: Int) {
placeholderRepository.constrainedAvailableSpace.value = height
}
+
+ private fun checkValidBounds(bounds: ShadeScrimBounds?) {
+ check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index d302fb67dddb..5fec0965f6a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -49,8 +49,14 @@ interface NotificationScrollView {
/** Max alpha for this view */
fun setMaxAlpha(alpha: Float)
- /** Set the clipping bounds used when drawing */
- fun setScrimClippingShape(shape: ShadeScrimShape?)
+ /** Sets a clipping shape, which defines the drawable area of this view. */
+ fun setClippingShape(shape: ShadeScrimShape?)
+
+ /**
+ * Sets a clipping shape, which defines the non-drawable area of this view. The final drawing
+ * area is the difference of the clipping shape, and the negative clipping shape.
+ */
+ fun setNegativeClippingShape(shape: ShadeScrimShape?)
/** set the y position in px of the top of the stack in this view's coordinates */
fun setStackTop(stackTop: Float)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index ef68b4de5291..8709d27bddcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -79,8 +79,17 @@ constructor(
launch {
viewModel
- .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
- .collectTraced { view.setScrimClippingShape(it) }
+ .notificationScrimShape(
+ cornerRadius = scrimRadius,
+ viewLeftOffset = viewLeftOffset,
+ )
+ .collectTraced { view.setClippingShape(it) }
+ }
+
+ launch {
+ viewModel.qsScrimShape(viewLeftOffset = viewLeftOffset).collectTraced {
+ view.setNegativeClippingShape(it)
+ }
}
launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 1bb205cbcb61..80ebf43baf92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -59,7 +59,7 @@ class NotificationScrollViewModel
@AssistedInject
constructor(
dumpManager: DumpManager,
- stackAppearanceInteractor: NotificationStackAppearanceInteractor,
+ private val stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
private val remoteInputInteractor: RemoteInputInteractor,
private val sceneInteractor: SceneInteractor,
@@ -221,7 +221,7 @@ constructor(
private val shadeScrimClipping: Flow<ShadeScrimClipping?> =
combine(
qsAllowsClipping,
- stackAppearanceInteractor.shadeScrimBounds,
+ stackAppearanceInteractor.notificationShadeScrimBounds,
stackAppearanceInteractor.shadeScrimRounding,
) { qsAllowsClipping, bounds, rounding ->
bounds?.takeIf { qsAllowsClipping }?.let { ShadeScrimClipping(it, rounding) }
@@ -229,7 +229,7 @@ constructor(
.distinctUntilChanged()
.dumpWhileCollecting("stackClipping")
- fun shadeScrimShape(
+ fun notificationScrimShape(
cornerRadius: Flow<Int>,
viewLeftOffset: Flow<Int>,
): Flow<ShadeScrimShape?> =
@@ -243,6 +243,12 @@ constructor(
}
.dumpWhileCollecting("shadeScrimShape")
+ fun qsScrimShape(viewLeftOffset: Flow<Int>): Flow<ShadeScrimShape?> =
+ combine(stackAppearanceInteractor.qsPanelShape, viewLeftOffset) { shape, leftOffset ->
+ shape?.let { it.copy(bounds = it.bounds.minus(leftOffset = leftOffset)) }
+ }
+ .dumpWhileCollecting("qsScrimShape")
+
/**
* Max alpha to apply directly to the view based on the compose placeholder.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 49cd7cb4fb8d..5d550226a79e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
@@ -40,7 +41,6 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -89,7 +89,7 @@ constructor(
/** Notifies that the bounds of the notification scrim have changed. */
fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) {
- interactor.setShadeScrimBounds(bounds)
+ interactor.setNotificationShadeScrimBounds(bounds)
}
/** Sets the available space */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index aa1308931f99..3f44f7bdef90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -234,7 +234,7 @@ private constructor(
)
}
if (ShadeWindowGoesAround.isEnabled && event.action == MotionEvent.ACTION_DOWN) {
- lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(context.displayId)
+ lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(event, mView.width)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
index 30c529a9034a..3e7094a0b5e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -34,6 +34,9 @@ interface CarrierConfigRepository {
*/
suspend fun startObservingCarrierConfigUpdates()
- /** Gets a cached [SystemUiCarrierConfig], or creates a new one which will track the defaults */
- fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig
+ /**
+ * Gets a cached [SystemUiCarrierConfig], or creates a new one which will track the defaults. A
+ * null [maybeSubId] will return the default carrier config.
+ */
+ fun getOrCreateConfigForSubId(maybeSubId: Int?): SystemUiCarrierConfig
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt
index 9a97f19f6593..b32037992501 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt
@@ -20,6 +20,7 @@ import android.content.IntentFilter
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.util.SparseArray
import androidx.annotation.VisibleForTesting
import androidx.core.util.getOrElse
@@ -51,7 +52,7 @@ constructor(
private val defaultConfig: PersistableBundle by lazy { CarrierConfigManager.getDefaultConfig() }
// Used for logging the default config in the dumpsys
private val defaultConfigForLogs: SystemUiCarrierConfig by lazy {
- SystemUiCarrierConfig(-1, defaultConfig)
+ SystemUiCarrierConfig(INVALID_SUBSCRIPTION_ID, defaultConfig)
}
private val configs = SparseArray<SystemUiCarrierConfig>()
@@ -89,7 +90,10 @@ constructor(
configToUpdate.processNewCarrierConfig(config)
}
- override fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig {
+ override fun getOrCreateConfigForSubId(maybeSubId: Int?): SystemUiCarrierConfig {
+ // See CarrierConfigManager#getConfigForSubId(), passing INVALID_SUBSCRIPTION_ID yields
+ // the default carrier config
+ val subId = maybeSubId ?: INVALID_SUBSCRIPTION_ID
return configs.getOrElse(subId) {
val config = SystemUiCarrierConfig(subId, defaultConfig)
val carrierConfig = carrierConfigManager?.getConfigForSubId(subId)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 32e9c85bea81..09a626940c79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -48,8 +48,8 @@ interface MobileConnectionsRepository {
*/
val activeSubChangedInGroupEvent: Flow<Unit>
- /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
- val defaultDataSubId: StateFlow<Int>
+ /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId]. Null if there is no default */
+ val defaultDataSubId: StateFlow<Int?>
/**
* True if the default network connection is a mobile-like connection and false otherwise.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index fc766915e4ef..252ebe6a32b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -164,7 +164,7 @@ constructor(
override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()
- override val defaultDataSubId: StateFlow<Int> =
+ override val defaultDataSubId: StateFlow<Int?> =
activeRepo
.flatMapLatest { it.defaultDataSubId }
.stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 936954f3b484..b608e53b4bce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -166,7 +166,7 @@ constructor(
private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
// TODO(b/261029387): add a command for this value
- override val defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+ override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(null)
// TODO(b/261029387): not yet supported
override val mobileIsDefault: StateFlow<Boolean> = MutableStateFlow(true)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index b762751ecbb4..aa6da61958e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -249,7 +249,7 @@ constructor(
tableLogger,
LOGGING_PREFIX,
columnName = "activeSubId",
- initialValue = INVALID_SUBSCRIPTION_ID,
+ initialValue = null,
)
.stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
@@ -264,22 +264,31 @@ constructor(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
- override val defaultDataSubId: StateFlow<Int> =
+ override val defaultDataSubId: StateFlow<Int?> =
broadcastDispatcher
.broadcastFlow(
IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
) { intent, _ ->
- intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ val subId =
+ intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ if (subId == INVALID_SUBSCRIPTION_ID) {
+ null
+ } else {
+ subId
+ }
}
.distinctUntilChanged()
.logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
columnName = "defaultSubId",
- initialValue = INVALID_SUBSCRIPTION_ID,
+ initialValue = null,
)
- .onStart { emit(subscriptionManagerProxy.getDefaultDataSubscriptionId()) }
- .stateIn(scope, SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+ .onStart {
+ val subId = subscriptionManagerProxy.getDefaultDataSubscriptionId()
+ emit(if (subId == INVALID_SUBSCRIPTION_ID) null else subId)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
private val carrierConfigChangedEvent =
broadcastDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 78731faa6167..be56461a96ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -72,7 +72,7 @@ interface MobileIconsInteractor {
val filteredSubscriptions: Flow<List<SubscriptionModel>>
/** Subscription ID of the current default data subscription */
- val defaultDataSubId: Flow<Int>
+ val defaultDataSubId: Flow<Int?>
/**
* The current list of [MobileIconInteractor]s associated with the current list of
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 71e19188f309..b1cc208e9b43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -32,6 +32,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.theme.PlatformTheme
@@ -39,6 +40,7 @@ import com.android.keyguard.AlphaOptimizedLinearLayout
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
@@ -142,10 +144,14 @@ fun StatusBarRoot(
Box(Modifier.fillMaxSize()) {
// TODO(b/364360986): remove this before rolling the flag forward
- Disambiguation(viewModel = statusBarViewModel)
+ if (StatusBarRootModernization.SHOW_DISAMBIGUATION) {
+ Disambiguation(viewModel = statusBarViewModel)
+ }
Row(Modifier.fillMaxSize()) {
val scope = rememberCoroutineScope()
+ val visible =
+ statusBarViewModel.shouldHomeStatusBarBeVisible.collectAsStateWithLifecycle(false)
AndroidView(
factory = { context ->
val inflater = LayoutInflater.from(context)
@@ -280,7 +286,12 @@ fun StatusBarRoot(
}
onViewCreated(phoneStatusBarView)
phoneStatusBarView
- }
+ },
+ update = { view ->
+ // Show or hide the entire status bar. This is important so that we aren't
+ // visible when first inflated
+ view.isVisible = visible.value
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index d9d9a29ee2b6..6ff4354fcc46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -80,6 +80,9 @@ import kotlinx.coroutines.flow.stateIn
* so that it's all in one place and easily testable outside of the fragment.
*/
interface HomeStatusBarViewModel {
+ /** Should the entire status bar be hidden? */
+ val shouldHomeStatusBarBeVisible: Flow<Boolean>
+
/**
* True if the device is currently transitioning from lockscreen to occluded and false
* otherwise.
@@ -271,10 +274,12 @@ constructor(
isHomeScreenStatusBarAllowedLegacy
}
- private val shouldHomeStatusBarBeVisible =
- combine(isHomeStatusBarAllowed, keyguardInteractor.isSecureCameraActive) {
+ override val shouldHomeStatusBarBeVisible =
+ combine(
isHomeStatusBarAllowed,
- isSecureCameraActive ->
+ keyguardInteractor.isSecureCameraActive,
+ headsUpNotificationInteractor.statusBarHeadsUpStatus,
+ ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
// When launching the camera over the lockscreen, the status icons would typically
// become visible momentarily before animating out, since we're not yet aware that the
// launching camera activity is fullscreen. Even once the activity finishes launching,
@@ -282,7 +287,7 @@ constructor(
// tells us to hide them.
// To ensure that this high-visibility animation is smooth, keep the icons hidden during
// a camera launch. See b/257292822.
- isHomeStatusBarAllowed && !isSecureCameraActive
+ headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
}
private val isAnyChipVisible =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt
index 7ae74c3bfb65..0b83c4e3dea3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt
@@ -22,6 +22,7 @@ import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
/**
* View model for the operator name (aka carrier name) of the carrier for the default data
@@ -34,6 +35,10 @@ class StatusBarOperatorNameViewModel
constructor(mobileIconsInteractor: MobileIconsInteractor) {
val operatorName: Flow<String?> =
mobileIconsInteractor.defaultDataSubId.flatMapLatest {
- mobileIconsInteractor.getMobileConnectionInteractorForSubId(it).carrierName
+ if (it == null) {
+ flowOf(null)
+ } else {
+ mobileIconsInteractor.getMobileConnectionInteractorForSubId(it).carrierName
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 284e23e5a288..47c82e309d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -26,7 +26,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInteropFilter
@@ -48,8 +51,13 @@ fun GestureTutorialScreen(
onBack: () -> Unit,
) {
BackHandler(onBack = onBack)
+ var cachedTutorialState: TutorialActionState by
+ rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
+ mutableStateOf(NotStarted)
+ }
val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false)
- val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted)
+ val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(cachedTutorialState)
+ cachedTutorialState = tutorialState
TouchpadGesturesHandlingBox(
motionEventConsumer,
tutorialState,
diff --git a/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt b/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
index ef9340a332dc..ffaddfbb9b15 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
@@ -20,11 +20,13 @@ import android.content.Context
import android.util.AttributeSet
import com.android.systemui.res.R
-class DelayableMarqueeTextView @JvmOverloads constructor(
+open class DelayableMarqueeTextView
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : SafeMarqueeTextView(context, attrs, defStyleAttr, defStyleRes) {
var marqueeDelay: Long = DEFAULT_MARQUEE_DELAY
@@ -39,16 +41,20 @@ class DelayableMarqueeTextView @JvmOverloads constructor(
}
init {
- val typedArray = context.theme.obtainStyledAttributes(
+ val typedArray =
+ context.theme.obtainStyledAttributes(
attrs,
R.styleable.DelayableMarqueeTextView,
defStyleAttr,
- defStyleRes
- )
- marqueeDelay = typedArray.getInteger(
- R.styleable.DelayableMarqueeTextView_marqueeDelay,
- DEFAULT_MARQUEE_DELAY.toInt()
- ).toLong()
+ defStyleRes,
+ )
+ marqueeDelay =
+ typedArray
+ .getInteger(
+ R.styleable.DelayableMarqueeTextView_marqueeDelay,
+ DEFAULT_MARQUEE_DELAY.toInt(),
+ )
+ .toLong()
typedArray.recycle()
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 96630ca36b97..908249dbca08 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -144,7 +144,6 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) {
ringerState.orientation,
)
}
-
is RingerDrawerState.Closed -> {
if (
uiModel.selectedButton.ringerMode ==
@@ -189,7 +188,6 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) {
}
}
}
-
is RingerDrawerState.Open -> {
drawerContainer.animateAndBindDrawerButtons(
viewModel,
@@ -220,7 +218,6 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) {
}
}
}
-
is RingerViewModelState.Unavailable -> {
drawerContainer.visibility = View.GONE
volumeDialogBackgroundView.setBackgroundResource(
@@ -251,7 +248,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) {
.requireViewById<ImageButton>(R.id.volume_drawer_button)
val previousIndex =
uiModel.availableButtons.indexOfFirst {
- it?.ringerMode == uiModel.drawerState.previousMode
+ it.ringerMode == uiModel.drawerState.previousMode
}
val unselectedButton =
getChildAt(count - previousIndex)
@@ -306,20 +303,18 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) {
) {
val count = uiModel.availableButtons.size
uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
- ringerButton?.let {
- val view = getChildAt(count - index)
- val isOpen = uiModel.drawerState is RingerDrawerState.Open
- if (index == uiModel.currentButtonIndex) {
- view.bindDrawerButton(
- if (isOpen) it else uiModel.selectedButton,
- viewModel,
- isOpen,
- isSelected = true,
- isAnimated = isAnimated,
- )
- } else {
- view.bindDrawerButton(it, viewModel, isOpen, isAnimated = isAnimated)
- }
+ val view = getChildAt(count - index)
+ val isOpen = uiModel.drawerState is RingerDrawerState.Open
+ if (index == uiModel.currentButtonIndex) {
+ view.bindDrawerButton(
+ if (isOpen) ringerButton else uiModel.selectedButton,
+ viewModel,
+ isOpen,
+ isSelected = true,
+ isAnimated = isAnimated,
+ )
+ } else {
+ view.bindDrawerButton(ringerButton, viewModel, isOpen, isAnimated = isAnimated)
}
}
onAnimationEnd?.run()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
index 96d4f62416bf..8613610ad1bf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
@@ -19,7 +19,7 @@ package com.android.systemui.volume.dialog.ringer.ui.viewmodel
/** Models volume dialog ringer */
data class RingerViewModel(
/** List of the available buttons according to the available modes */
- val availableButtons: List<RingerButtonViewModel?>,
+ val availableButtons: List<RingerButtonViewModel>,
/** The index of the currently selected button */
val currentButtonIndex: Int,
/** Currently selected button. */
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index eec64d9a2f86..07c4de0ac0c4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -50,7 +50,9 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -106,9 +108,25 @@ constructor(
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build()
+ init {
+ ringerViewModel
+ .onEach { viewModelState ->
+ when (viewModelState) {
+ is RingerViewModelState.Available ->
+ volumeDialogLogger.onRingerDrawerAvailable(
+ viewModelState.uiModel.availableButtons.map { it.ringerMode }
+ )
+ is RingerViewModelState.Unavailable ->
+ volumeDialogLogger.onRingerDrawerUnavailable()
+ }
+ }
+ .launchIn(coroutineScope)
+ }
+
fun onRingerButtonClicked(ringerMode: RingerMode, isSelectedButton: Boolean = false) {
if (drawerState.value is RingerDrawerState.Open && !isSelectedButton) {
Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
+ volumeDialogLogger.onRingerModeChanged(ringerMode)
provideTouchFeedback(ringerMode)
maybeShowToast(ringerMode)
ringerInteractor.setRingerMode(ringerMode)
@@ -159,7 +177,9 @@ constructor(
RingerViewModelState.Available(
RingerViewModel(
availableButtons =
- availableModes.map { mode -> toButtonViewModel(mode, isZenMuted) },
+ availableModes.mapNotNull { mode ->
+ toButtonViewModel(mode, isZenMuted)
+ },
currentButtonIndex = currentIndex,
selectedButton = it,
drawerState = drawerState,
@@ -219,7 +239,6 @@ constructor(
hintLabelResId = R.string.volume_ringer_hint_unmute,
ringerMode = ringerMode,
)
-
availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
RingerButtonViewModel(
imageResId = R.drawable.ic_speaker_on,
@@ -232,7 +251,6 @@ constructor(
hintLabelResId = R.string.volume_ringer_hint_vibrate,
ringerMode = ringerMode,
)
-
else ->
RingerButtonViewModel(
imageResId = R.drawable.ic_speaker_on,
@@ -269,17 +287,14 @@ constructor(
null
}
}
-
RINGER_MODE_SILENT ->
applicationContext.getString(
internalR.string.volume_dialog_ringer_guidance_silent
)
-
RINGER_MODE_VIBRATE ->
applicationContext.getString(
internalR.string.volume_dialog_ringer_guidance_vibrate
)
-
else ->
applicationContext.getString(
internalR.string.volume_dialog_ringer_guidance_vibrate
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
index 9a3aa7e3d79f..cccf090fae85 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -45,6 +45,52 @@ class VolumeDialogLogger @Inject constructor(@VolumeLog private val logBuffer: L
)
}
+ fun onVolumeSliderAdjustmentFinished(volume: Int, stream: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = volume
+ int2 = stream
+ },
+ { "Volume adjusted: volume=$int1 stream=$int2" },
+ )
+ }
+
+ fun onVolumeSlidersUpdated(primaryStream: Int, floating: Collection<Int>) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = primaryStream
+ str1 = floating.joinToString(",") { it.toString() }
+ },
+ { "Showing streams: primary=$int1 floating=$str1" },
+ )
+ }
+
+ fun onRingerModeChanged(ringerMode: RingerMode) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = ringerMode.value },
+ { "Ringer mode changed to: $int1" },
+ )
+ }
+
+ fun onRingerDrawerAvailable(modes: List<RingerMode>) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = modes.joinToString(",") { it.value.toString() } },
+ { "Ringer drawer available with modes: $str1" },
+ )
+ }
+
+ fun onRingerDrawerUnavailable() {
+ logBuffer.log(TAG, LogLevel.DEBUG, {}, { "Ringer drawer unavailable" })
+ }
+
fun onCurrentRingerModeIsUnsupported(ringerMode: RingerMode) {
logBuffer.log(
TAG,
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
index 3b964fdec1b8..d40302408dd6 100644
--- 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
@@ -27,6 +27,7 @@ import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlide
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.google.android.material.slider.Slider
+import com.google.android.material.slider.Slider.OnSliderTouchListener
import javax.inject.Inject
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
@@ -68,6 +69,15 @@ constructor(
sliderView.addOnChangeListener { _, value, fromUser ->
viewModel.setStreamVolume(value.roundToInt(), fromUser)
}
+ sliderView.addOnSliderTouchListener(
+ object : OnSliderTouchListener {
+ override fun onStartTrackingTouch(slider: Slider) {}
+
+ override fun onStopTrackingTouch(slider: Slider) {
+ viewModel.onStreamChangeFinished(slider.value.roundToInt())
+ }
+ }
+ )
viewModel.isDisabledByZenMode.onEach { sliderView.isEnabled = !it }.launchIn(this)
viewModel.state
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
index 71fe22ba4b01..9cf02f26c9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
@@ -82,10 +82,6 @@ constructor(
ringerMode: RingerMode?,
): Int {
val isStreamOffline = level == 0 || isMuted
- when (ringerMode?.value) {
- AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate
- AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off
- }
if (isRoutedToBluetooth) {
return if (stream == AudioManager.STREAM_VOICE_CALL) {
R.drawable.ic_volume_bt_sco
@@ -98,29 +94,39 @@ constructor(
}
}
+ val isLevelLow = level < (levelMax + levelMin) / 2
return if (isStreamOffline) {
+ val ringerOfflineIcon =
+ when (ringerMode?.value) {
+ AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate
+ AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off
+ else -> null
+ }
when (stream) {
AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
- AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
+ AudioManager.STREAM_NOTIFICATION ->
+ ringerOfflineIcon ?: R.drawable.ic_volume_ringer_mute
+ AudioManager.STREAM_RING -> ringerOfflineIcon ?: R.drawable.ic_volume_ringer_vibrate
AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
else -> null
- } ?: getIconForStream(stream)
- } else {
- if (level < (levelMax + levelMin) / 2) {
- // This icon is different on TV
- R.drawable.ic_volume_media_low
- } else {
- getIconForStream(stream)
}
- }
+ } else {
+ null
+ } ?: getIconForStream(stream = stream, isLevelLow = isLevelLow)
}
@DrawableRes
- private fun getIconForStream(stream: Int): Int {
+ private fun getIconForStream(stream: Int, isLevelLow: Boolean): Int {
return when (stream) {
AudioManager.STREAM_ACCESSIBILITY -> R.drawable.ic_volume_accessibility
- AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media
+ AudioManager.STREAM_MUSIC ->
+ if (isLevelLow) {
+ // This icon is different on TV
+ R.drawable.ic_volume_media_low
+ } else {
+ R.drawable.ic_volume_media
+ }
AudioManager.STREAM_RING -> R.drawable.ic_ring_volume
AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer
AudioManager.STREAM_ALARM -> R.drawable.ic_alarm
@@ -135,7 +141,9 @@ constructor(
* affect the [stream]
*/
private fun ringerModeForStream(stream: Int): Flow<RingerMode?> {
- return if (stream == AudioManager.STREAM_RING) {
+ return if (
+ stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION
+ ) {
audioVolumeInteractor.ringerMode
} else {
flowOf(null)
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
index d999910675b0..89dd0352afa7 100644
--- 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
@@ -20,9 +20,11 @@ import com.android.systemui.util.time.SystemClock
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,6 +62,8 @@ constructor(
@VolumeDialog private val coroutineScope: CoroutineScope,
private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider,
private val systemClock: SystemClock,
+ private val sliderType: VolumeDialogSliderType,
+ private val logger: VolumeDialogLogger,
) {
private val userVolumeUpdates = MutableStateFlow<VolumeUpdate?>(null)
@@ -110,6 +114,10 @@ constructor(
}
}
+ fun onStreamChangeFinished(volume: Int) {
+ logger.onVolumeSliderAdjustmentFinished(volume = volume, stream = sliderType.audioStream)
+ }
+
private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
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
index d8e6aec026c6..344dadcce229 100644
--- 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
@@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
import javax.inject.Inject
@@ -33,13 +34,18 @@ class VolumeDialogSlidersViewModel
@Inject
constructor(
@VolumeDialog coroutineScope: CoroutineScope,
- private val slidersInteractor: VolumeDialogSlidersInteractor,
+ slidersInteractor: VolumeDialogSlidersInteractor,
private val sliderComponentFactory: VolumeDialogSliderComponent.Factory,
+ private val volumeDialogLogger: VolumeDialogLogger,
) {
val sliders: Flow<VolumeDialogSliderUiModel> =
slidersInteractor.sliders
.map { slidersModel ->
+ volumeDialogLogger.onVolumeSlidersUpdated(
+ slidersModel.slider.audioStream,
+ slidersModel.floatingSliders.map { it.audioStream },
+ )
VolumeDialogSliderUiModel(
sliderComponent = sliderComponentFactory.create(slidersModel.slider),
floatingSliderComponent =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 5b8d9b045475..328d1245e4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -65,8 +65,6 @@ constructor(
) : SliderViewModel {
private val volumeChanges = MutableStateFlow<Int?>(null)
- private val streamsAffectedByRing =
- setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
private val audioStream = audioStreamWrapper.audioStream
private val iconsByStream =
mapOf(
@@ -175,9 +173,9 @@ constructor(
null
},
a11yStateDescription =
- if (volume == volumeRange.first) {
+ if (isMuted) {
context.getString(
- if (audioStream.value in streamsAffectedByRing) {
+ if (isAffectedByRingerMode) {
if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
R.string.volume_panel_hint_vibrate
} else {
@@ -226,8 +224,8 @@ constructor(
private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
val iconRes =
- if (isAffectedByMute && isMuted) {
- if (audioStream.value in streamsAffectedByRing) {
+ if (isMuted) {
+ if (isAffectedByRingerMode) {
if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
R.drawable.ic_volume_ringer_vibrate
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
new file mode 100644
index 000000000000..c1fb0e80cafc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers
+
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.service.wallpaper.WallpaperService
+import android.util.Log
+import android.view.SurfaceHolder
+import androidx.core.graphics.toRectF
+
+/** A wallpaper that shows a static gradient color image wallpaper. */
+class GradientColorWallpaper : WallpaperService() {
+
+ override fun onCreateEngine(): Engine = GradientColorWallpaperEngine()
+
+ inner class GradientColorWallpaperEngine : Engine() {
+ init {
+ setShowForAllUsers(true)
+ }
+
+ override fun onSurfaceRedrawNeeded(surfaceHolder: SurfaceHolder) {
+ drawFrameInternal(surfaceHolder)
+ }
+
+ private fun drawFrameInternal(surfaceHolder: SurfaceHolder) {
+ val context = displayContext ?: return
+ val surface = surfaceHolder.surface
+ var canvas: Canvas? = null
+ try {
+ canvas = surface.lockHardwareCanvas()
+ val destRectF = surfaceHolder.surfaceFrame.toRectF()
+ val toColor = context.getColor(com.android.internal.R.color.materialColorPrimary)
+
+ // TODO(b/384519696): Draw the actual gradient color wallpaper instead.
+ canvas.drawRect(destRectF, Paint().apply { color = toColor })
+ } catch (exception: IllegalStateException) {
+ Log.d(TAG, "Fail to draw in the canvas", exception)
+ } finally {
+ canvas?.let { surface.unlockCanvasAndPost(it) }
+ }
+ }
+ }
+
+ private companion object {
+ const val TAG = "GradientColorWallpaper"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 9f6ad56335d7..c14cb87064ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -194,7 +194,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
- mMenuAnimationController.flingMenuThenSpringToEdge(/* x= */ 0, /* velocityX= */
+ mMenuAnimationController.flingMenuThenSpringToEdge(new PointF(), /* velocityX= */
100, /* velocityY= */ 100);
mMenuAnimationController.mPositionAnimations.values()
.forEach(animation -> verify((FlingAnimation) animation).addEndListener(
@@ -212,7 +212,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
- mMenuAnimationController.flingMenuThenSpringToEdge(/* x= */ 0, /* velocityX= */
+ mMenuAnimationController.flingMenuThenSpringToEdge(new PointF(), /* velocityX= */
200, /* velocityY= */ 200);
mMenuAnimationController.mPositionAnimations.values()
.forEach(animation -> verify((FlingAnimation) animation).addEndListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
new file mode 100644
index 000000000000..146488b523ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Rect;
+import android.testing.TestableLooper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MenuViewAppearanceTest}. */
+@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class MenuViewAppearanceTest extends SysuiTestCase {
+ static final Rect DRAGGABLE_BOUNDS = new Rect(0, 0, 10, 10);
+ static final int MENU_HEIGHT = 1;
+
+ @Test
+ public void avoidVerticalDisplayCutout_roomAbove_placesAbove() {
+ final int y = 2;
+ final Rect cutout = new Rect(0, 3, 0, 10);
+
+ final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+ y, MENU_HEIGHT, DRAGGABLE_BOUNDS, cutout);
+
+ assertThat(end_y + MENU_HEIGHT).isAtMost(cutout.top);
+ }
+
+ @Test
+ public void avoidVerticalDisplayCutout_roomBelow_placesBelow() {
+ final int y = 2;
+ final Rect cutout = new Rect(0, 0, 0, 5);
+
+ final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+ y, MENU_HEIGHT, DRAGGABLE_BOUNDS, cutout);
+
+ assertThat(end_y).isAtLeast(cutout.bottom);
+ }
+
+ @Test
+ public void avoidVerticalDisplayCutout_noRoom_noChange() {
+ final int y = 2;
+ final Rect cutout = new Rect(0, 0, 0, 10);
+
+ final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+ y, MENU_HEIGHT, DRAGGABLE_BOUNDS, cutout);
+
+ assertThat(end_y).isEqualTo(end_y);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 1500340c9d89..01fc868c716b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -232,7 +232,6 @@ public class MenuViewLayerTest extends SysuiTestCase {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void triggerDismissMenuAction_callsA11yManagerEnableShortcutsForTargets() {
final List<String> stubShortcutTargets = new ArrayList<>();
stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
@@ -249,45 +248,6 @@ public class MenuViewLayerTest extends SysuiTestCase {
}
@Test
- @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
- public void triggerDismissMenuAction_matchA11yButtonTargetsResult() {
- mMenuViewLayer.mDismissMenuAction.run();
- verify(mSecureSettings).putStringForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
- UserHandle.USER_CURRENT);
- }
-
- @Test
- @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
- public void triggerDismissMenuAction_matchEnabledA11yServicesResult() {
- setupEnabledAccessibilityServiceList();
-
- mMenuViewLayer.mDismissMenuAction.run();
- final String value = Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
-
- assertThat(value).isEqualTo("");
- }
-
- @Test
- @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
- public void triggerDismissMenuAction_hasHardwareKeyShortcut_keepEnabledStatus() {
- setupEnabledAccessibilityServiceList();
- final List<String> stubShortcutTargets = new ArrayList<>();
- stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
- when(mStubAccessibilityManager.getAccessibilityShortcutTargets(
- ShortcutConstants.UserShortcutType.HARDWARE)).thenReturn(stubShortcutTargets);
-
- mMenuViewLayer.mDismissMenuAction.run();
- final String value = Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
-
- assertThat(value).isEqualTo(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
- }
-
- @Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
public void onEditAction_startsActivity() {
mockActivityQuery(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
deleted file mode 100644
index 134c40da1033..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.android.systemui.bouncer.data.repository
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardBouncerRepositoryTest : SysuiTestCase() {
-
- @Mock private lateinit var systemClock: SystemClock
- @Mock private lateinit var bouncerLogger: TableLogBuffer
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- lateinit var underTest: KeyguardBouncerRepository
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- underTest =
- KeyguardBouncerRepositoryImpl(
- systemClock,
- testScope.backgroundScope,
- bouncerLogger,
- )
- }
-
- @Test
- fun changingFlowValueTriggersLogging() =
- testScope.runTest {
- underTest.setPrimaryShow(true)
- Mockito.verify(bouncerLogger)
- .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
- }
-}
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 3763282cdebc..6ac20d40f2dc 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
@@ -881,7 +881,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@EnableSceneContainer
public void testIsInsideScrollableRegion_noOffset() {
mStackScroller.setLeftTopRightBottom(0, 0, 1000, 2000);
- mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000));
+ mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000));
MotionEvent event1 = transformEventForView(createMotionEvent(500f, 400f), mStackScroller);
assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse();
@@ -900,7 +900,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@EnableSceneContainer
public void testIsInsideScrollableRegion_offset() {
mStackScroller.setLeftTopRightBottom(1000, 0, 2000, 2000);
- mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000));
+ mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000));
MotionEvent event1 = transformEventForView(createMotionEvent(1500f, 400f), mStackScroller);
assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 243be3dc142d..437ccb6a9821 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -37,7 +37,6 @@ import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
import com.android.systemui.battery.BatteryMeterView
@@ -52,7 +51,9 @@ import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -62,7 +63,6 @@ import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.view.ViewUtil
@@ -74,7 +74,6 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -82,6 +81,8 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -287,7 +288,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun handleTouchEventFromStatusBar_touchOnPrimaryDisplay_statusBarConnectedDisplaysDisabled_shadeReceivesEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
`when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -299,10 +300,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(
- AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS,
- AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND,
- )
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME, ShadeWindowGoesAround.FLAG_NAME)
fun handleTouchEventFromStatusBar_touchOnPrimaryDisplay_statusBarConnectedDisplaysEnabled_shadeWindowGoesAroundEnabled_shadeReceivesEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
`when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -314,8 +312,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
- @DisableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
fun handleTouchEventFromStatusBar_touchOnPrimaryDisplay_statusBarConnectedDisplaysEnabled_shadeWindowGoesAroundDisabled_shadeReceivesEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
`when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -327,7 +325,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun handleTouchEventFromStatusBar_touchOnSecondaryDisplay_statusBarConnectedDisplaysDisabled_shadeReceivesEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
`when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -339,10 +337,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(
- AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS,
- AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND,
- )
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME, ShadeWindowGoesAround.FLAG_NAME)
fun handleTouchEventFromStatusBar_touchOnSecondaryDisplay_statusBarConnectedDisplaysEnabled_shadeWindowGoesAroundEnabled_shadeReceivesEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
`when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -354,8 +349,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
- @DisableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
fun handleTouchEventFromStatusBar_touchOnSecondaryDisplay_statusBarConnectedDisplaysEnabled_shadeWindowGoesAroundDisabled_shadeDoesNotReceiveEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
`when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -441,27 +436,30 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
fun onTouch_actionDown_propagatesToDisplayPolicy() {
- controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ controller.onTouch(event)
- verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(mContext.displayId))
+ verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(event), any())
}
@Test
- @EnableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
fun onTouch_actionUp_notPropagatesToDisplayPolicy() {
- controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0))
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ controller.onTouch(event)
- verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+ verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any(), any())
}
@Test
- @DisableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
fun onTouch_shadeWindowGoesAroundDisabled_notPropagatesToDisplayPolicy() {
- controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ controller.onTouch(event)
- verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+ verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(eq(event), any())
}
@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 d7456dfb33c3..6c60f55a3904 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
@@ -829,7 +829,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.defaultDataSubId)
- assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID)
+ assertThat(latest).isEqualTo(null)
val intent2 =
Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
@@ -856,6 +856,31 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
+ fun defaultDataSubId_filtersOutInvalidSubIds() =
+ testScope.runTest {
+ subscriptionManagerProxy.defaultDataSubId = INVALID_SUBSCRIPTION_ID
+ val latest by collectLastValue(underTest.defaultDataSubId)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun defaultDataSubId_filtersOutInvalidSubIds_fromValidToInvalid() =
+ testScope.runTest {
+ subscriptionManagerProxy.defaultDataSubId = 2
+ val latest by collectLastValue(underTest.defaultDataSubId)
+
+ assertThat(latest).isEqualTo(2)
+
+ val intent =
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun defaultDataSubId_fetchesCurrentOnRestart() =
testScope.runTest {
subscriptionManagerProxy.defaultDataSubId = 2
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index 703e2d1db200..f95f95721ce2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -23,9 +23,18 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
- private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
override val primaryBouncerStartingDisappearAnimation =
- _primaryBouncerDisappearAnimation.asStateFlow()
+ MutableSharedFlow<Runnable?>(extraBufferCapacity = 2, replay = 1)
+
+ override fun isPrimaryBouncerStartingDisappearAnimation(): Boolean {
+ val replayCache = primaryBouncerStartingDisappearAnimation.replayCache
+ return if (!replayCache.isEmpty()) {
+ replayCache.last() != null
+ } else {
+ false
+ }
+ }
+
private val _primaryBouncerScrimmed = MutableStateFlow(false)
override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncerConstants.EXPANSION_HIDDEN)
@@ -53,6 +62,8 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos
MutableStateFlow(KeyguardSecurityModel.SecurityMode.Invalid)
override var bouncerDismissActionModel: BouncerDismissActionModel? = null
+ override fun isDebuggable() = true
+
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
@@ -74,7 +85,7 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos
}
override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
- _primaryBouncerDisappearAnimation.value = runnable
+ primaryBouncerStartingDisappearAnimation.tryEmit(runnable)
}
override fun setPanelExpansion(panelExpansion: Float) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
index fb6699c44d62..91f1e1c3e0c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
@@ -17,18 +17,16 @@
package com.android.systemui.compose
import androidx.compose.runtime.snapshots.Snapshot
-import com.android.systemui.kosmos.runCurrent
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
/**
* Runs the given test [block] in a [TestScope] that's set up such that the Compose snapshot state
- * is settled eagerly. This is the Compose equivalent to using an [UnconfinedTestDispatcher] or
- * using [runCurrent] a lot.
+ * writes are properly applied to the global snapshot. This is for instance necessary if your test
+ * is using `snapshotFlow {}` or any other mechanism that is observing the global snapshot.
*
- * Note that this shouldn't be needed or used in a Compose test environment.
+ * Note that this isn't needed in a Compose test environment, e.g. if you use the
+ * `Compose(Content)TestRule`.
*/
fun TestScope.runTestWithSnapshots(block: suspend TestScope.() -> Unit) {
val handle = Snapshot.registerGlobalWriteObserver { Snapshot.sendApplyNotifications() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index 4667bf5292fa..9a6f2f60aac8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -96,7 +96,7 @@ class FakeContextualEducationRepository : ContextualEducationRepository {
override suspend fun updateGestureEduModel(
gestureType: GestureType,
- transform: (GestureEduModel) -> GestureEduModel
+ transform: (GestureEduModel) -> GestureEduModel,
) {
val gestureModels =
when (gestureType) {
@@ -117,6 +117,11 @@ class FakeContextualEducationRepository : ContextualEducationRepository {
_eduDeviceConnectionTime.value = transform(currentModel)
}
+ override suspend fun clear() {
+ val currentUserMap = userGestureMap[currentUser]!!
+ currentUserMap.clear()
+ }
+
override val keyboardShortcutTriggered: Flow<GestureType>
get() = _keyboardShortcutTriggered.filterNotNull()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index 046284861e2b..4f3b8f3541e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -20,12 +20,14 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by
Kosmos.Fixture {
QuickSettingsShadeOverlayContentViewModel(
shadeInteractor = shadeInteractor,
sceneInteractor = sceneInteractor,
+ notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
quickSettingsContainerViewModelFactory = quickSettingsContainerViewModelFactory,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
index 636cb37adf03..aaef27d257c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
@@ -23,7 +23,11 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy
import com.android.systemui.shade.display.DefaultDisplayShadePolicy
import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.ShadeExpansionIntent
import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
+import com.android.systemui.shade.domain.interactor.notificationElement
+import com.android.systemui.shade.domain.interactor.qsElement
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.util.settings.fakeGlobalSettings
val Kosmos.defaultShadeDisplayPolicy: DefaultDisplayShadePolicy by
@@ -37,16 +41,20 @@ val Kosmos.anyExternalShadeDisplayPolicy: AnyExternalShadeDisplayPolicy by
)
}
-val Kosmos.focusBasedShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
+val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
Kosmos.Fixture {
StatusBarTouchShadeDisplayPolicy(
displayRepository = displayRepository,
backgroundScope = testScope.backgroundScope,
keyguardRepository = keyguardRepository,
shadeOnDefaultDisplayWhenLocked = false,
+ shadeInteractor = { shadeInteractor },
+ notificationElement = { notificationElement },
+ qsShadeElement = { qsElement },
)
}
-
+val Kosmos.shadeExpansionIntent: ShadeExpansionIntent by
+ Kosmos.Fixture { statusBarTouchShadeDisplayPolicy }
val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
Kosmos.Fixture {
ShadeDisplaysRepositoryImpl(
@@ -62,7 +70,7 @@ val Kosmos.shadeDisplayPolicies: Set<ShadeDisplayPolicy> by
setOf(
defaultShadeDisplayPolicy,
anyExternalShadeDisplayPolicy,
- focusBasedShadeDisplayPolicy,
+ statusBarTouchShadeDisplayPolicy,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
index 6e44df833582..923de2dcbf68 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
@@ -23,6 +23,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker
import com.android.systemui.shade.ShadeWindowLayoutParams
import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.shadeExpansionIntent
import java.util.Optional
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
@@ -49,5 +50,6 @@ val Kosmos.shadeDisplaysInteractor by
testScope.backgroundScope.coroutineContext,
mockedShadeDisplayChangeLatencyTracker,
Optional.of(shadeExpandedStateInteractor),
+ shadeExpansionIntent,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 1dc7229a6506..32a30502a370 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -31,7 +31,6 @@ import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.userSwitcherInteractor
-import org.mockito.kotlin.mock
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
Kosmos.Fixture {
@@ -73,7 +72,19 @@ val Kosmos.shadeInteractorImpl by
shadeModeInteractor = shadeModeInteractor,
)
}
-var Kosmos.mockShadeInteractor: ShadeInteractor by Kosmos.Fixture { mock() }
+var Kosmos.notificationElement: NotificationShadeElement by
+ Kosmos.Fixture {
+ NotificationShadeElement(shadeInteractor, testScope.backgroundScope.coroutineContext)
+ }
+var Kosmos.qsElement: QSShadeElement by
+ Kosmos.Fixture { QSShadeElement(shadeInteractor, testScope.backgroundScope.coroutineContext) }
val Kosmos.shadeExpandedStateInteractor by
- Kosmos.Fixture { ShadeExpandedStateInteractorImpl(shadeInteractor, testScope.backgroundScope) }
+ Kosmos.Fixture {
+ ShadeExpandedStateInteractorImpl(
+ shadeInteractor,
+ testScope.backgroundScope,
+ notificationElement,
+ qsElement,
+ )
+ }
val Kosmos.fakeShadeExpandedStateInteractor by Kosmos.Fixture { FakeShadeExpandedStateInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt
index adf6ca1d1710..eebfca79efe3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.os.PersistableBundle
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
class FakeCarrierConfigRepository : CarrierConfigRepository {
@@ -24,8 +25,12 @@ class FakeCarrierConfigRepository : CarrierConfigRepository {
val configsById = mutableMapOf<Int, SystemUiCarrierConfig>()
- override fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig =
- configsById.getOrPut(subId) { SystemUiCarrierConfig(subId, createDefaultTestConfig()) }
+ override fun getOrCreateConfigForSubId(maybeSubId: Int?): SystemUiCarrierConfig {
+ val subId = maybeSubId ?: INVALID_SUBSCRIPTION_ID
+ return configsById.getOrPut(subId) {
+ SystemUiCarrierConfig(subId, createDefaultTestConfig())
+ }
+ }
}
val CarrierConfigRepository.fake
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 3b8adb4a8307..352f6cf011e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -55,7 +55,7 @@ class FakeMobileIconsInteractor(
override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
- override val defaultDataSubId = MutableStateFlow(DEFAULT_DATA_SUB_ID)
+ override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(DEFAULT_DATA_SUB_ID)
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
index 63cd440a8633..b26081c40c38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
@@ -20,15 +20,19 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.util.time.systemClock
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.volumeDialogLogger
import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
val Kosmos.volumeDialogSliderViewModel by
Kosmos.Fixture {
VolumeDialogSliderViewModel(
+ sliderType = volumeDialogSliderType,
interactor = volumeDialogSliderInteractor,
visibilityInteractor = volumeDialogVisibilityInteractor,
coroutineScope = applicationCoroutineScope,
volumeDialogSliderIconProvider = volumeDialogSliderIconProvider,
systemClock = systemClock,
+ logger = volumeDialogLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
index 5531f7608b69..8fb60fdd5ab1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.dialog.shared.volumeDialogLogger
import com.android.systemui.volume.dialog.sliders.dagger.volumeDialogSliderComponentFactory
import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSlidersInteractor
@@ -27,5 +28,6 @@ val Kosmos.volumeDialogSlidersViewModel by
applicationCoroutineScope,
volumeDialogSlidersInteractor,
volumeDialogSliderComponentFactory,
+ volumeDialogLogger,
)
}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
index 369ef6ae6a3f..97f86b1bff5b 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
@@ -209,6 +209,8 @@ public class Vcn extends Handler {
this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
}
+ // WARNING: This constructor executes on the binder thread. Thread safety MUST be ensured when
+ // accessing data within this constructor and any methods called from here.
@VisibleForTesting(visibility = Visibility.PRIVATE)
public Vcn(
@NonNull VcnContext vcnContext,
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
index 99c848f53c39..38fcf09145d9 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
@@ -36,7 +36,6 @@ import android.net.vcn.VcnGatewayConnectionConfig;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -46,6 +45,7 @@ import com.android.modules.utils.HandlerExecutor;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
/**
@@ -56,12 +56,14 @@ import java.util.concurrent.Executor;
*
* @hide
*/
+// TODO(b/388919146): Implement a more generic solution to prevent concurrent modifications on
+// mListeners and mRequests
// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class VcnNetworkProvider extends NetworkProvider {
private static final String TAG = VcnNetworkProvider.class.getSimpleName();
- private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
+ private final Set<NetworkRequestListener> mListeners = ConcurrentHashMap.newKeySet();
private final Context mContext;
private final Handler mHandler;
@@ -72,7 +74,7 @@ public class VcnNetworkProvider extends NetworkProvider {
*
* <p>NetworkRequests are immutable once created, and therefore can be used as stable keys.
*/
- private final Set<NetworkRequest> mRequests = new ArraySet<>();
+ private final Set<NetworkRequest> mRequests = ConcurrentHashMap.newKeySet();
public VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
this(context, looper, new Dependencies());
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 80126df1b8df..26b6fe3d82ad 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -52,7 +52,7 @@ class android.content.BroadcastReceiver keep
class android.content.Context keep
method <init> ()V keep
method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; keep
-class android.content.pm.PackageManager keep
+class android.content.pm.PackageManager
method <init> ()V keep
class android.text.ClipboardManager keep
method <init> ()V keep
diff --git a/ravenwood/tools/hoststubgen/scripts/dump-jar b/ravenwood/tools/hoststubgen/scripts/dump-jar
index 998357b70dff..02a6d25378bd 100755
--- a/ravenwood/tools/hoststubgen/scripts/dump-jar
+++ b/ravenwood/tools/hoststubgen/scripts/dump-jar
@@ -89,14 +89,33 @@ filter_output() {
# - Some other transient lines
# - Sometimes the javap shows mysterious warnings, so remove them too.
#
- # `/PATTERN-1/,/PATTERN-2/{//!d}` is a trick to delete lines between two patterns, without
- # the start and the end lines.
+ # Most conversion are simple per-line deletion or simple inline replacement with a regex.
+ #
+ # But removing the constant pool is a bit tricky. It looks like this in the output:
+ #---------------------------
+ #Constant pool:
+ # #1 = Methodref #31.#88 // java/lang/Object."<init>":()V
+ # #2 = Class #89 // java/lang/UnsupportedOperationException
+ # :
+ #{ // Or something, I'm not sure if it always ends with a "{".
+ #---------------------------
+ # i.e. we want to delete all lines from "Constant pool:" as long as the first character
+ # is a space.
+ #
+ # If we simply use '/^Constant pool:/,/^[^ ]/d', then it'll delete the "Constant pool:"
+ # line and "{" line too, but again the last line might be important, so we don't want to
+ # delete it.
+ #
+ # So we instead, use '/^Constant pool:/,/^[^ ]/{/^ /d}', which mean:
+ # between lines matching '/^Constant pool:/' and '/^[^ ]/', delete lines that start with
+ # a space. (=='/^ /d').
+ #
sed -e 's/#[0-9][0-9]*/#x/g' \
-e 's/^\( *\)[0-9][0-9]*:/\1x:/' \
- -e '/^Constant pool:/,/^[^ ]/{//!d}' \
+ -e '/^Constant pool:/,/^[^ ]/{/^ /d}' \
-e '/^ *line *[0-9][0-9]*: *[0-9][0-9]*$/d' \
- -e '/SHA-256 checksum/d' \
- -e '/Last modified/d' \
+ -e '/^ *SHA-256 checksum/d' \
+ -e '/^ *Last modified/d' \
-e '/^Classfile jar/d' \
-e '/\[warning\]/d'
else
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index be1b6ca93869..c5500831e21a 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -413,8 +413,8 @@ class TextFileFilterPolicyParser {
}
private fun parseClass(fields: Array<String>) {
- if (fields.size < 3) {
- throw ParseException("Class ('c') expects 2 fields.")
+ if (fields.size <= 1) {
+ throw ParseException("Class ('c') expects 1 or 2 fields.")
}
val className = fields[1]
@@ -424,7 +424,9 @@ class TextFileFilterPolicyParser {
// :aidl, etc?
val classType = resolveSpecialClass(className)
- if (fields[2].startsWith("!")) {
+ val policyStr = if (fields.size > 2) { fields[2] } else { "" }
+
+ if (policyStr.startsWith("!")) {
if (classType != SpecialClass.NotSpecial) {
// We could support it, but not needed at least for now.
throw ParseException(
@@ -432,10 +434,10 @@ class TextFileFilterPolicyParser {
)
}
// It's a redirection class.
- val toClass = fields[2].substring(1)
+ val toClass = policyStr.substring(1)
processor.onRedirectionClass(className, toClass)
- } else if (fields[2].startsWith("~")) {
+ } else if (policyStr.startsWith("~")) {
if (classType != SpecialClass.NotSpecial) {
// We could support it, but not needed at least for now.
throw ParseException(
@@ -443,11 +445,23 @@ class TextFileFilterPolicyParser {
)
}
// It's a class-load hook
- val callback = fields[2].substring(1)
+ val callback = policyStr.substring(1)
processor.onClassLoadHook(className, callback)
} else {
- val policy = parsePolicy(fields[2])
+ // Special case: if it's a class directive with no policy, then it encloses
+ // members, but we don't apply any policy to the class itself.
+ // This is only allowed in a not-special case.
+ if (policyStr == "") {
+ if (classType == SpecialClass.NotSpecial && superClass == null) {
+ currentClassName = className
+ processor.onSimpleClassStart(className)
+ return
+ }
+ throw ParseException("Special class or subclass directive must have a policy")
+ }
+
+ val policy = parsePolicy(policyStr)
if (!policy.isUsableWithClasses) {
throw ParseException("Class can't have policy '$policy'")
}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
index 8408a18142eb..23699fd1dba4 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
@@ -37,8 +37,7 @@ SCRIPT_NAME="${0##*/}"
GOLDEN_DIR=${GOLDEN_DIR:-golden-output}
mkdir -p $GOLDEN_DIR
-# TODO(b/388562869) We shouldn't need `--ignore-matching-lines`, but the golden files may not have the "Constant pool:" lines.
-DIFF_CMD=${DIFF_CMD:-diff -u --ignore-blank-lines --ignore-space-change --ignore-matching-lines='^\(Constant.pool:\|{\)$'}
+DIFF_CMD=${DIFF_CMD:-./tiny-framework-dump-test.py run-diff}
update=0
three_way=0
@@ -63,12 +62,10 @@ done
shift $(($OPTIND - 1))
# Build the dump files, which are the input of this test.
-run ${BUILD_CMD:=m} dump-jar tiny-framework-dump-test
-
+run ${BUILD_CMD:-m} dump-jar tiny-framework-dump-test
# Get the path to the generate text files. (not the golden files.)
# We get them from $OUT/module-info.json
-
files=(
$(python3 -c '
import sys
@@ -78,7 +75,7 @@ import json
with open(sys.argv[1], "r") as f:
data = json.load(f)
- # Equivalent to: jq -r '.["tiny-framework-dump-test"]["installed"][]'
+ # Equivalent to: jq -r '.["tiny-framework-dump-test"]["installed"][]'
for path in data["tiny-framework-dump-test"]["installed"]:
if "golden-output" in path:
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index c35d6d106c8d..761748265726 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -28,9 +28,16 @@ GOLDEN_DIRS = [
# Run diff.
def run_diff(file1, file2):
- # TODO(b/388562869) We shouldn't need `--ignore-matching-lines`, but the golden files may not have the "Constant pool:" lines.
- command = ['diff', '-u', '--ignore-blank-lines',
+ command = ['diff', '-u',
+ '--ignore-blank-lines',
'--ignore-space-change',
+
+ # Ignore the class file version.
+ '--ignore-matching-lines=^ *\(major\|minor\) version:$',
+
+ # We shouldn't need `--ignore-matching-lines`, but somehow
+ # the golden files were generated without these lines for b/388562869,
+ # so let's just ignore them.
'--ignore-matching-lines=^\(Constant.pool:\|{\)$',
file1, file2]
print(' '.join(command))
@@ -85,4 +92,13 @@ class TestWithGoldenOutput(unittest.TestCase):
if __name__ == "__main__":
+ args = sys.argv
+
+ # This script is used by diff-and-update-golden.sh too.
+ if len(args) > 1 and args[1] == "run-diff":
+ if run_diff(args[2], args[3]):
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
unittest.main(verbosity=2)
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b08aa5b37b33..7275881e9661 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1841,9 +1841,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public void notifyQuickSettingsTilesChanged(
@UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
notifyQuickSettingsTilesChanged_enforcePermission();
- if (!android.view.accessibility.Flags.a11yQsShortcut()) {
- return;
- }
if (DEBUG) {
Slog.d(LOG_TAG, TextUtils.formatSimple(
"notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
@@ -2278,9 +2275,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void restoreShortcutTargets(
String newValue, @UserShortcutType int shortcutType, int userId) {
assertNoTapShortcut(shortcutType);
- if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) {
- return;
- }
synchronized (mLock) {
final AccessibilityUserState userState = getUserStateLocked(userId);
@@ -3894,9 +3888,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
private void updateAccessibilityShortcutTargetsLocked(
AccessibilityUserState userState, @UserShortcutType int shortcutType) {
- if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) {
- return;
- }
if (shortcutType == SOFTWARE) {
// Update accessibility button availability.
for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
@@ -4079,9 +4070,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final List<Integer> shortcutTypes = new ArrayList<>(4);
shortcutTypes.add(HARDWARE);
shortcutTypes.add(SOFTWARE);
- if (android.view.accessibility.Flags.a11yQsShortcut()) {
- shortcutTypes.add(QUICK_SETTINGS);
- }
+ shortcutTypes.add(QUICK_SETTINGS);
if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
shortcutTypes.add(GESTURE);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index d6bffcb7d21d..42385fc5bdb0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -37,6 +37,7 @@ filegroup {
":framework_native_aidl",
":gsiservice_aidl",
":installd_aidl",
+ ":mmd_aidl",
":storaged_aidl",
":vold_aidl",
],
@@ -246,6 +247,7 @@ java_library_static {
"aconfig_new_storage_flags_lib",
"powerstats_flags_lib",
"locksettings_flags_lib",
+ "mmd_flags_lib",
"profiling_flags_lib",
"android.adpf.sessionmanager_aidl-java",
"uprobestats_flags_java_lib",
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 6858e2941ff9..ef769cf6217c 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -9,6 +9,7 @@ per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com
# Zram writeback
per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
+per-file ZramMaintenance.java = kawasin@google.com
# ServiceWatcher
per-file ServiceWatcher.java = sooniln@google.com
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b7bc4e4827ef..19e7e062758a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -41,6 +41,7 @@ import static android.os.storage.OnObbStateChangeListener.ERROR_NOT_MOUNTED;
import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED;
import static android.os.storage.OnObbStateChangeListener.MOUNTED;
import static android.os.storage.OnObbStateChangeListener.UNMOUNTED;
+import static android.mmd.flags.Flags.mmdEnabled;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -945,12 +946,17 @@ class StorageManagerService extends IStorageManager.Stub
});
refreshZramSettings();
- // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
- String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
- if (!zramPropValue.equals("0")
- && mContext.getResources().getBoolean(
+ if (mmdEnabled()) {
+ // TODO: b/375432472 - Start zram maintenance only when zram is enabled.
+ ZramMaintenance.startZramMaintenance(mContext);
+ } else {
+ // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
+ String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
+ if (!zramPropValue.equals("0")
+ && mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {
- ZramWriteback.scheduleZramWriteback(mContext);
+ ZramWriteback.scheduleZramWriteback(mContext);
+ }
}
configureTranscoding();
@@ -977,7 +983,7 @@ class StorageManagerService extends IStorageManager.Stub
// sole writer.
SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue);
// Schedule writeback only if zram is being enabled.
- if (desiredPropertyValue.equals("1")
+ if (!mmdEnabled() && desiredPropertyValue.equals("1")
&& mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {
ZramWriteback.scheduleZramWriteback(mContext);
diff --git a/services/core/java/com/android/server/ZramMaintenance.java b/services/core/java/com/android/server/ZramMaintenance.java
new file mode 100644
index 000000000000..cdb48122e321
--- /dev/null
+++ b/services/core/java/com/android/server/ZramMaintenance.java
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IMmd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import java.time.Duration;
+
+/**
+ * Schedules zram maintenance (e.g. zram writeback, zram recompression).
+ *
+ * <p>ZramMaintenance notifies mmd the good timing to execute zram maintenance based on:
+ *
+ * <ul>
+ * <li>Enough interval has passed.
+ * <li>The system is idle.
+ * <li>The battery is not low.
+ * </ul>
+ */
+public class ZramMaintenance extends JobService {
+ private static final String TAG = ZramMaintenance.class.getName();
+ // Job id must be unique across all clients of the same uid. ZramMaintenance uses the bug number
+ // as the job id.
+ private static final int JOB_ID = 375432472;
+ private static final ComponentName sZramMaintenance =
+ new ComponentName("android", ZramMaintenance.class.getName());
+
+ private static final String FIRST_DELAY_SECONDS_PROP =
+ "mm.zram.maintenance.first_delay_seconds";
+ // The default is 1 hour.
+ private static final long DEFAULT_FIRST_DELAY_SECONDS = 3600;
+ private static final String PERIODIC_DELAY_SECONDS_PROP =
+ "mm.zram.maintenance.periodic_delay_seconds";
+ // The default is 1 hour.
+ private static final long DEFAULT_PERIODIC_DELAY_SECONDS = 3600;
+ private static final String REQUIRE_DEVICE_IDLE_PROP =
+ "mm.zram.maintenance.require_device_idle";
+ private static final boolean DEFAULT_REQUIRE_DEVICE_IDLE =
+ true;
+ private static final String REQUIRE_BATTERY_NOT_LOW_PROP =
+ "mm.zram.maintenance.require_battry_not_low";
+ private static final boolean DEFAULT_REQUIRE_BATTERY_NOT_LOW =
+ true;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ IBinder binder = ServiceManager.getService("mmd");
+ if (binder != null) {
+ IMmd mmd = IMmd.Stub.asInterface(binder);
+ try {
+ mmd.doZramMaintenance();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to doZramMaintenance", e);
+ }
+ } else {
+ Slog.w(TAG, "binder not found");
+ }
+ Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP,
+ DEFAULT_PERIODIC_DELAY_SECONDS));
+ scheduleZramMaintenance(this, delay);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ /**
+ * Starts periodical zram maintenance.
+ */
+ public static void startZramMaintenance(Context context) {
+ Duration delay = Duration.ofSeconds(
+ SystemProperties.getLong(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay);
+ }
+
+ private static void scheduleZramMaintenance(Context context, Duration delay) {
+ JobScheduler js = context.getSystemService(JobScheduler.class);
+
+ if (js != null) {
+ js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance)
+ .setMinimumLatency(delay.toMillis())
+ .setRequiresDeviceIdle(
+ SystemProperties.getBoolean(REQUIRE_DEVICE_IDLE_PROP,
+ DEFAULT_REQUIRE_DEVICE_IDLE))
+ .setRequiresBatteryNotLow(
+ SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP,
+ DEFAULT_REQUIRE_BATTERY_NOT_LOW))
+ .build());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS
index daa02111f71f..02df9860030d 100644
--- a/services/core/java/com/android/server/crashrecovery/OWNERS
+++ b/services/core/java/com/android/server/crashrecovery/OWNERS
@@ -1,3 +1,2 @@
-ancr@google.com
harshitmahajan@google.com
robertogil@google.com
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b530da2a5f5e..ca001b9c7e6d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4188,6 +4188,9 @@ public final class DisplayManagerService extends SystemService {
}
}
+ /**
+ * This method must not be called with mCallback held or deadlock will ensue.
+ */
@Override
public void binderDied() {
synchronized (mCallback) {
@@ -4248,17 +4251,8 @@ public final class DisplayManagerService extends SystemService {
}
}
- return transmitDisplayEvent(displayId, event);
- }
-
- /**
- * Transmit a single display event. The client is presumed ready. Return true on success
- * and false if the client died.
- */
- private boolean transmitDisplayEvent(int displayId, @DisplayEvent int event) {
- // The client is ready to receive the event.
try {
- mCallback.onDisplayEvent(displayId, event);
+ transmitDisplayEvent(displayId, event);
return true;
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process "
@@ -4269,6 +4263,18 @@ public final class DisplayManagerService extends SystemService {
}
/**
+ * Transmit a single display event. The client is presumed ready. This throws if the
+ * client has died; callers must catch and handle the exception. The exception cannot be
+ * handled directly here because {@link #binderDied()} must not be called whilst holding
+ * the mCallback lock.
+ */
+ private void transmitDisplayEvent(int displayId, @DisplayEvent int event)
+ throws RemoteException {
+ // The client is ready to receive the event.
+ mCallback.onDisplayEvent(displayId, event);
+ }
+
+ /**
* Return true if the client is interested in this event.
*/
private boolean shouldSendDisplayEvent(@DisplayEvent int event) {
@@ -4376,27 +4382,32 @@ public final class DisplayManagerService extends SystemService {
// would be unusual to do so. The method returns true on success.
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
public boolean dispatchPending() {
- synchronized (mCallback) {
- if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
- return true;
- }
- if (!isReadyLocked()) {
- return false;
- }
- for (int i = 0; i < mPendingEvents.size(); i++) {
- Event displayEvent = mPendingEvents.get(i);
- if (DEBUG) {
- Slog.d(TAG, "Send pending display event #" + i + " "
- + displayEvent.displayId + "/"
- + displayEvent.event + " to " + mUid + "/" + mPid);
+ try {
+ synchronized (mCallback) {
+ if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
+ return true;
+ }
+ if (!isReadyLocked()) {
+ return false;
}
- if (!transmitDisplayEvent(displayEvent.displayId, displayEvent.event)) {
- Slog.d(TAG, "Drop pending events for dead process " + mPid);
- break;
+ for (int i = 0; i < mPendingEvents.size(); i++) {
+ Event displayEvent = mPendingEvents.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
+ }
+ transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
}
+ mPendingEvents.clear();
+ return true;
}
- mPendingEvents.clear();
- return true;
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process "
+ + mPid + " that display topology changed, assuming it died.", ex);
+ binderDied();
+ return false;
+
}
}
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 4e57d6791ff6..43aa6f46da78 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -258,6 +258,11 @@ public class DisplayManagerFlags {
Flags::subscribeGranularDisplayEvents
);
+ private final FlagState mBaseDensityForExternalDisplays = new FlagState(
+ Flags.FLAG_BASE_DENSITY_FOR_EXTERNAL_DISPLAYS,
+ Flags::baseDensityForExternalDisplays
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -553,6 +558,14 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if the flag for base density for external displays is enabled
+ */
+ public boolean isBaseDensityForExternalDisplaysEnabled() {
+ return mBaseDensityForExternalDisplays.isEnabled();
+ }
+
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -606,6 +619,7 @@ public class DisplayManagerFlags {
pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
pw.println(" " + mSubscribeGranularDisplayEvents);
pw.println(" " + mEnableDisplayContentModeManagementFlagState);
+ pw.println(" " + mBaseDensityForExternalDisplays);
}
private static class FlagState {
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 afae07c88f8d..00a9dcb71b4b 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
@@ -471,3 +471,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "base_density_for_external_displays"
+ namespace: "lse_desktop_experience"
+ description: "Feature flag for setting a base density for external displays."
+ bug: "382954433"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index a41194b898ac..1949d103a0d6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -499,9 +499,11 @@ import java.util.List;
/* package */
static HubMessage createHubMessage(Message message) {
boolean isReliable = (message.flags & Message.FLAG_REQUIRES_DELIVERY_STATUS) != 0;
- return new HubMessage.Builder(message.type, message.content)
+ HubMessage outMessage = new HubMessage.Builder(message.type, message.content)
.setResponseRequired(isReliable)
.build();
+ outMessage.setMessageSequenceNumber(message.sequenceNumber);
+ return outMessage;
}
/**
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 28e21b71dcc9..39ee0cbeacce 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -68,6 +68,17 @@ public class LocationFudger {
private static final double MAX_LATITUDE =
90.0 - (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR);
+ // The average edge length in km of an S2 cell, indexed by S2 levels 0 to
+ // 13. Level 13 is the highest level used for coarsening.
+ // This approximation assumes the S2 cells are squares.
+ // For density-based coarsening, we use the edge to set the accuracy of the
+ // coarsened location.
+ // The values are from http://s2geometry.io/resources/s2cell_statistics.html
+ // We take square root of the average area.
+ private static final float[] S2_CELL_AVG_EDGE_PER_LEVEL = new float[] {
+ 9220.14f, 4610.07f, 2305.04f, 1152.52f, 576.26f, 288.13f, 144.06f,
+ 72.03f, 36.02f, 20.79f, 9f, 5.05f, 2.25f, 1.13f, 0.57f};
+
private final float mAccuracyM;
private final Clock mClock;
private final Random mRandom;
@@ -194,12 +205,14 @@ public class LocationFudger {
// The new algorithm is applied if and only if (1) the flag is on, (2) the cache has been
// set, and (3) the cache has successfully queried the provider for the default coarsening
// value.
+ float accuracy = mAccuracyM;
if (Flags.populationDensityProvider() && Flags.densityBasedCoarseLocations()
&& cacheCopy != null) {
if (cacheCopy.hasDefaultValue()) {
// New algorithm that snaps to the center of a S2 cell.
int level = cacheCopy.getCoarseningLevel(latitude, longitude);
coarsened = snapToCenterOfS2Cell(latitude, longitude, level);
+ accuracy = getS2CellApproximateEdge(level);
} else {
// Try to fetch the default value. The answer won't come in time, but will be used
// for the next location to coarsen.
@@ -214,7 +227,7 @@ public class LocationFudger {
coarse.setLatitude(coarsened[LAT_INDEX]);
coarse.setLongitude(coarsened[LNG_INDEX]);
- coarse.setAccuracy(Math.max(mAccuracyM, coarse.getAccuracy()));
+ coarse.setAccuracy(Math.max(accuracy, coarse.getAccuracy()));
synchronized (this) {
mCachedFineLocation = fine;
@@ -224,6 +237,19 @@ public class LocationFudger {
return coarse;
}
+ // Returns the average edge length in meters of an S2 cell at the given
+ // level. This is computed as if the S2 cell were a square. We do not need
+ // an exact value, only a rough approximation.
+ @VisibleForTesting
+ protected float getS2CellApproximateEdge(int level) {
+ if (level < 0) {
+ level = 0;
+ } else if (level >= S2_CELL_AVG_EDGE_PER_LEVEL.length) {
+ level = S2_CELL_AVG_EDGE_PER_LEVEL.length - 1;
+ }
+ return S2_CELL_AVG_EDGE_PER_LEVEL[level] * 1000;
+ }
+
// quantize location by snapping to a grid. this is the primary means of obfuscation. it
// gives nice consistent results and is very effective at hiding the true location (as
// long as you are not sitting on a grid boundary, which the random offsets mitigate).
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
index ba98a0a9fd4e..fd1bea9ae639 100644
--- a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
@@ -25,8 +25,9 @@ import android.media.IRemoteDisplayProvider;
import android.media.RemoteDisplayState;
import android.os.Handler;
import android.os.IBinder;
-import android.os.RemoteException;
import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
@@ -35,10 +36,8 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.Objects;
-/**
- * Maintains a connection to a particular remote display provider service.
- */
-final class RemoteDisplayProviderProxy implements ServiceConnection {
+/** Maintains a connection to a particular remote display provider service. */
+final class RemoteDisplayProviderProxy {
private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -61,12 +60,15 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
private RemoteDisplayState mDisplayState;
private boolean mScheduledDisplayStateChangedCallback;
- public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
- int userId) {
+ private final ServiceConnection mServiceConnection =
+ new ServiceConnectionImpl();
+
+ /* package */ RemoteDisplayProviderProxy(
+ Context context, ComponentName componentName, int userId, Looper looper) {
mContext = context;
mComponentName = componentName;
mUserId = userId;
- mHandler = new Handler();
+ mHandler = new Handler(looper);
}
public void dump(PrintWriter pw, String prefix) {
@@ -190,9 +192,12 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
service.setComponent(mComponentName);
try {
- mBound = mContext.bindServiceAsUser(service, this,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- new UserHandle(mUserId));
+ mBound =
+ mContext.bindServiceAsUser(
+ service,
+ mServiceConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ new UserHandle(mUserId));
if (!mBound && DEBUG) {
Slog.d(TAG, this + ": Bind failed");
}
@@ -212,12 +217,11 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
mBound = false;
disconnect();
- mContext.unbindService(this);
+ mContext.unbindService(mServiceConnection);
}
}
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
+ private void onServiceConnectedOnHandler(IBinder service) {
if (DEBUG) {
Slog.d(TAG, this + ": Connected");
}
@@ -241,8 +245,7 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
}
}
- @Override
- public void onServiceDisconnected(ComponentName name) {
+ private void onServiceDisconnectedOnHandler() {
if (DEBUG) {
Slog.d(TAG, this + ": Service disconnected");
}
@@ -322,6 +325,20 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
}
+ // All methods in this class are called on the main thread.
+ private final class ServiceConnectionImpl implements ServiceConnection {
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mHandler.post(() -> onServiceConnectedOnHandler(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mHandler.post(RemoteDisplayProviderProxy.this::onServiceDisconnectedOnHandler);
+ }
+ }
+
private final class Connection implements DeathRecipient {
private final IRemoteDisplayProvider mProvider;
private final ProviderCallback mCallback;
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
index 64c451d03caa..cc03c805fef0 100644
--- a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -121,9 +121,11 @@ public final class RemoteDisplayProviderWatcher {
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
if (sourceIndex < 0) {
RemoteDisplayProviderProxy provider =
- new RemoteDisplayProviderProxy(mContext,
- new ComponentName(serviceInfo.packageName, serviceInfo.name),
- mUserId);
+ new RemoteDisplayProviderProxy(
+ mContext,
+ new ComponentName(serviceInfo.packageName, serviceInfo.name),
+ mUserId,
+ mHandler.getLooper());
provider.start();
mProviders.add(targetIndex++, provider);
mCallback.addProvider(provider);
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index d9df09dad853..efc1b9959c0f 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -110,7 +110,7 @@ public class MediaQualityService extends SystemService {
if ((pp.getPackageName() != null && !pp.getPackageName().isEmpty()
&& !incomingPackageEqualsCallingUidPackage(pp.getPackageName()))
&& !hasGlobalPictureQualityServicePermission()) {
- notifyPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -128,7 +128,9 @@ public class MediaQualityService extends SystemService {
Long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
null, values);
populateTempIdMap(mPictureProfileTempIdMap, id);
- pp.setProfileId(mPictureProfileTempIdMap.getValue(id));
+ String value = mPictureProfileTempIdMap.getValue(id);
+ pp.setProfileId(value);
+ notifyOnPictureProfileAdded(value, pp, Binder.getCallingUid(), Binder.getCallingPid());
return pp;
}
@@ -136,7 +138,7 @@ public class MediaQualityService extends SystemService {
public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) {
Long dbId = mPictureProfileTempIdMap.getKey(id);
if (!hasPermissionToUpdatePictureProfile(dbId, pp)) {
- notifyPictureProfileError(id, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(id, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -150,6 +152,8 @@ public class MediaQualityService extends SystemService {
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
db.replace(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
null, values);
+ notifyOnPictureProfileUpdated(mPictureProfileTempIdMap.getValue(dbId),
+ getPictureProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
}
private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) {
@@ -164,8 +168,9 @@ public class MediaQualityService extends SystemService {
public void removePictureProfile(String id, UserHandle user) {
Long dbId = mPictureProfileTempIdMap.getKey(id);
- if (!hasPermissionToRemovePictureProfile(dbId)) {
- notifyPictureProfileError(id, PictureProfile.ERROR_NO_PERMISSION,
+ PictureProfile toDelete = getPictureProfile(dbId);
+ if (!hasPermissionToRemovePictureProfile(toDelete)) {
+ notifyOnPictureProfileError(id, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -176,16 +181,20 @@ public class MediaQualityService extends SystemService {
int result = db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
selectionArgs);
if (result == 0) {
- notifyPictureProfileError(id, PictureProfile.ERROR_INVALID_ARGUMENT,
+ notifyOnPictureProfileError(id, PictureProfile.ERROR_INVALID_ARGUMENT,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ notifyOnPictureProfileRemoved(mPictureProfileTempIdMap.getValue(dbId), toDelete,
+ Binder.getCallingUid(), Binder.getCallingPid());
mPictureProfileTempIdMap.remove(dbId);
}
}
- private boolean hasPermissionToRemovePictureProfile(Long dbId) {
- PictureProfile fromDb = getPictureProfile(dbId);
- return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid());
+ private boolean hasPermissionToRemovePictureProfile(PictureProfile toDelete) {
+ if (toDelete != null) {
+ return toDelete.getName().equalsIgnoreCase(getPackageOfCallingUid());
+ }
+ return false;
}
@Override
@@ -246,7 +255,7 @@ public class MediaQualityService extends SystemService {
public List<PictureProfile> getPictureProfilesByPackage(
String packageName, Bundle options, UserHandle user) {
if (!hasGlobalPictureQualityServicePermission()) {
- notifyPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -270,7 +279,7 @@ public class MediaQualityService extends SystemService {
@Override
public boolean setDefaultPictureProfile(String profileId, UserHandle user) {
if (!hasGlobalPictureQualityServicePermission()) {
- notifyPictureProfileError(profileId, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(profileId, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
// TODO: pass the profile ID to MediaQuality HAL when ready.
@@ -280,7 +289,7 @@ public class MediaQualityService extends SystemService {
@Override
public List<String> getPictureProfilePackageNames(UserHandle user) {
if (!hasGlobalPictureQualityServicePermission()) {
- notifyPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
String [] column = {BaseParameters.PARAMETER_PACKAGE};
@@ -325,7 +334,7 @@ public class MediaQualityService extends SystemService {
if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty()
&& !incomingPackageEqualsCallingUidPackage(sp.getPackageName()))
&& !hasGlobalPictureQualityServicePermission()) {
- notifySoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+ notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
@@ -342,7 +351,9 @@ public class MediaQualityService extends SystemService {
Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
null, values);
populateTempIdMap(mSoundProfileTempIdMap, id);
- sp.setProfileId(mSoundProfileTempIdMap.getValue(id));
+ String value = mSoundProfileTempIdMap.getValue(id);
+ sp.setProfileId(value);
+ notifyOnSoundProfileAdded(value, sp, Binder.getCallingUid(), Binder.getCallingPid());
return sp;
}
@@ -350,7 +361,7 @@ public class MediaQualityService extends SystemService {
public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) {
Long dbId = mSoundProfileTempIdMap.getKey(id);
if (!hasPermissionToUpdateSoundProfile(dbId, sp)) {
- notifySoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION,
+ notifyOnSoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -363,6 +374,8 @@ public class MediaQualityService extends SystemService {
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values);
+ notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId),
+ getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
}
private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) {
@@ -376,8 +389,9 @@ public class MediaQualityService extends SystemService {
@Override
public void removeSoundProfile(String id, UserHandle user) {
Long dbId = mSoundProfileTempIdMap.getKey(id);
- if (!hasPermissionToRemoveSoundProfile(dbId)) {
- notifySoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION,
+ SoundProfile toDelete = getSoundProfile(dbId);
+ if (!hasPermissionToRemoveSoundProfile(toDelete)) {
+ notifyOnSoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -388,16 +402,20 @@ public class MediaQualityService extends SystemService {
int result = db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
selectionArgs);
if (result == 0) {
- notifySoundProfileError(id, SoundProfile.ERROR_INVALID_ARGUMENT,
+ notifyOnSoundProfileError(id, SoundProfile.ERROR_INVALID_ARGUMENT,
Binder.getCallingUid(), Binder.getCallingPid());
}
+ notifyOnSoundProfileRemoved(mSoundProfileTempIdMap.getValue(dbId), toDelete,
+ Binder.getCallingUid(), Binder.getCallingPid());
mSoundProfileTempIdMap.remove(dbId);
}
}
- private boolean hasPermissionToRemoveSoundProfile(Long dbId) {
- SoundProfile fromDb = getSoundProfile(dbId);
- return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid());
+ private boolean hasPermissionToRemoveSoundProfile(SoundProfile toDelete) {
+ if (toDelete != null) {
+ return toDelete.getName().equalsIgnoreCase(getPackageOfCallingUid());
+ }
+ return false;
}
@Override
@@ -458,7 +476,7 @@ public class MediaQualityService extends SystemService {
public List<SoundProfile> getSoundProfilesByPackage(
String packageName, Bundle options, UserHandle user) {
if (!hasGlobalSoundQualityServicePermission()) {
- notifySoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+ notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -482,7 +500,7 @@ public class MediaQualityService extends SystemService {
@Override
public boolean setDefaultSoundProfile(String profileId, UserHandle user) {
if (!hasGlobalSoundQualityServicePermission()) {
- notifySoundProfileError(profileId, SoundProfile.ERROR_NO_PERMISSION,
+ notifyOnSoundProfileError(profileId, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
// TODO: pass the profile ID to MediaQuality HAL when ready.
@@ -492,7 +510,7 @@ public class MediaQualityService extends SystemService {
@Override
public List<String> getSoundProfilePackageNames(UserHandle user) {
if (!hasGlobalSoundQualityServicePermission()) {
- notifySoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+ notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
String [] column = {BaseParameters.PARAMETER_NAME};
@@ -735,7 +753,35 @@ public class MediaQualityService extends SystemService {
}
}
- private void notifyPictureProfileError(String profileId, int errorCode, int uid, int pid) {
+ enum Mode {
+ ADD,
+ UPDATE,
+ REMOVE,
+ ERROR
+ }
+
+ private void notifyOnPictureProfileAdded(String profileId, PictureProfile profile,
+ int uid, int pid) {
+ notifyPictureProfileHelper(Mode.ADD, profileId, profile, null, uid, pid);
+ }
+
+ private void notifyOnPictureProfileUpdated(String profileId, PictureProfile profile,
+ int uid, int pid) {
+ notifyPictureProfileHelper(Mode.UPDATE, profileId, profile, null, uid, pid);
+ }
+
+ private void notifyOnPictureProfileRemoved(String profileId, PictureProfile profile,
+ int uid, int pid) {
+ notifyPictureProfileHelper(Mode.REMOVE, profileId, profile, null, uid, pid);
+ }
+
+ private void notifyOnPictureProfileError(String profileId, int errorCode,
+ int uid, int pid) {
+ notifyPictureProfileHelper(Mode.ERROR, profileId, null, errorCode, uid, pid);
+ }
+
+ private void notifyPictureProfileHelper(Mode mode, String profileId, PictureProfile profile,
+ Integer errorCode, int uid, int pid) {
UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM);
int n = userState.mPictureProfileCallbacks.beginBroadcast();
@@ -747,17 +793,56 @@ public class MediaQualityService extends SystemService {
.get(callback);
if (pidUid.first == pid && pidUid.second == uid) {
- userState.mPictureProfileCallbacks.getBroadcastItem(i)
- .onError(profileId, errorCode);
+ if (mode == Mode.ADD) {
+ userState.mPictureProfileCallbacks.getBroadcastItem(i)
+ .onPictureProfileAdded(profileId, profile);
+ } else if (mode == Mode.UPDATE) {
+ userState.mPictureProfileCallbacks.getBroadcastItem(i)
+ .onPictureProfileUpdated(profileId, profile);
+ } else if (mode == Mode.REMOVE) {
+ userState.mPictureProfileCallbacks.getBroadcastItem(i)
+ .onPictureProfileRemoved(profileId, profile);
+ } else if (mode == Mode.ERROR) {
+ userState.mPictureProfileCallbacks.getBroadcastItem(i)
+ .onError(profileId, errorCode);
+ }
}
} catch (RemoteException e) {
- Slog.e(TAG, "failed to report added input to callback", e);
+ if (mode == Mode.ADD) {
+ Slog.e(TAG, "Failed to report added picture profile to callback", e);
+ } else if (mode == Mode.UPDATE) {
+ Slog.e(TAG, "Failed to report updated picture profile to callback", e);
+ } else if (mode == Mode.REMOVE) {
+ Slog.e(TAG, "Failed to report removed picture profile to callback", e);
+ } else if (mode == Mode.ERROR) {
+ Slog.e(TAG, "Failed to report picture profile error to callback", e);
+ }
}
}
userState.mPictureProfileCallbacks.finishBroadcast();
}
- private void notifySoundProfileError(String profileId, int errorCode, int uid, int pid) {
+ private void notifyOnSoundProfileAdded(String profileId, SoundProfile profile,
+ int uid, int pid) {
+ notifySoundProfileHelper(Mode.ADD, profileId, profile, null, uid, pid);
+ }
+
+ private void notifyOnSoundProfileUpdated(String profileId, SoundProfile profile,
+ int uid, int pid) {
+ notifySoundProfileHelper(Mode.UPDATE, profileId, profile, null, uid, pid);
+ }
+
+ private void notifyOnSoundProfileRemoved(String profileId, SoundProfile profile,
+ int uid, int pid) {
+ notifySoundProfileHelper(Mode.REMOVE, profileId, profile, null, uid, pid);
+ }
+
+ private void notifyOnSoundProfileError(String profileId, int errorCode, int uid, int pid) {
+ notifySoundProfileHelper(Mode.ERROR, profileId, null, errorCode, uid, pid);
+ }
+
+ private void notifySoundProfileHelper(Mode mode, String profileId, SoundProfile profile,
+ Integer errorCode, int uid, int pid) {
UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM);
int n = userState.mSoundProfileCallbacks.beginBroadcast();
@@ -769,11 +854,30 @@ public class MediaQualityService extends SystemService {
.get(callback);
if (pidUid.first == pid && pidUid.second == uid) {
- userState.mSoundProfileCallbacks.getBroadcastItem(i)
- .onError(profileId, errorCode);
+ if (mode == Mode.ADD) {
+ userState.mSoundProfileCallbacks.getBroadcastItem(i)
+ .onSoundProfileAdded(profileId, profile);
+ } else if (mode == Mode.UPDATE) {
+ userState.mSoundProfileCallbacks.getBroadcastItem(i)
+ .onSoundProfileUpdated(profileId, profile);
+ } else if (mode == Mode.REMOVE) {
+ userState.mSoundProfileCallbacks.getBroadcastItem(i)
+ .onSoundProfileRemoved(profileId, profile);
+ } else if (mode == Mode.ERROR) {
+ userState.mSoundProfileCallbacks.getBroadcastItem(i)
+ .onError(profileId, errorCode);
+ }
}
} catch (RemoteException e) {
- Slog.e(TAG, "failed to report added input to callback", e);
+ if (mode == Mode.ADD) {
+ Slog.e(TAG, "Failed to report added sound profile to callback", e);
+ } else if (mode == Mode.UPDATE) {
+ Slog.e(TAG, "Failed to report updated sound profile to callback", e);
+ } else if (mode == Mode.REMOVE) {
+ Slog.e(TAG, "Failed to report removed sound profile to callback", e);
+ } else if (mode == Mode.ERROR) {
+ Slog.e(TAG, "Failed to report sound profile error to callback", e);
+ }
}
}
userState.mSoundProfileCallbacks.finishBroadcast();
@@ -830,7 +934,7 @@ public class MediaQualityService extends SystemService {
@Override
public List<String> getPictureProfileAllowList(UserHandle user) {
if (!hasGlobalPictureQualityServicePermission()) {
- notifyPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
return new ArrayList<>();
@@ -839,7 +943,7 @@ public class MediaQualityService extends SystemService {
@Override
public void setPictureProfileAllowList(List<String> packages, UserHandle user) {
if (!hasGlobalPictureQualityServicePermission()) {
- notifyPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
}
@@ -847,7 +951,7 @@ public class MediaQualityService extends SystemService {
@Override
public List<String> getSoundProfileAllowList(UserHandle user) {
if (!hasGlobalSoundQualityServicePermission()) {
- notifySoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+ notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
return new ArrayList<>();
@@ -856,7 +960,7 @@ public class MediaQualityService extends SystemService {
@Override
public void setSoundProfileAllowList(List<String> packages, UserHandle user) {
if (!hasGlobalSoundQualityServicePermission()) {
- notifySoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+ notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
}
@@ -869,7 +973,7 @@ public class MediaQualityService extends SystemService {
@Override
public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) {
if (!hasGlobalPictureQualityServicePermission()) {
- notifyPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -901,7 +1005,7 @@ public class MediaQualityService extends SystemService {
@Override
public void setSuperResolutionEnabled(boolean enabled, UserHandle user) {
if (!hasGlobalPictureQualityServicePermission()) {
- notifyPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+ notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
@@ -933,7 +1037,7 @@ public class MediaQualityService extends SystemService {
@Override
public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) {
if (!hasGlobalSoundQualityServicePermission()) {
- notifySoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+ notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 05aa4134cbd5..f3eed7d22bec 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4983,6 +4983,12 @@ public class NotificationManagerService extends SystemService {
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(
String callingPkg, String targetPkg, int userId) {
+ return getOrCreateNotificationChannels(callingPkg, targetPkg, userId, false);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getOrCreateNotificationChannels(
+ String callingPkg, String targetPkg, int userId, boolean createPrefsIfNeeded) {
if (canNotifyAsPackage(callingPkg, targetPkg, userId)
|| isCallingUidSystem()) {
int targetUid = -1;
@@ -4992,7 +4998,8 @@ public class NotificationManagerService extends SystemService {
/* ignore */
}
return mPreferencesHelper.getNotificationChannels(
- targetPkg, targetUid, false /* includeDeleted */, true);
+ targetPkg, targetUid, false /* includeDeleted */, true,
+ createPrefsIfNeeded);
}
throw new SecurityException("Pkg " + callingPkg
+ " cannot read channels for " + targetPkg + " in " + userId);
@@ -10428,16 +10435,12 @@ public class NotificationManagerService extends SystemService {
}
private void scheduleListenerHintsChanged(int state) {
- if (!Flags.notificationReduceMessagequeueUsage()) {
- mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
- }
+ mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget();
}
private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) {
- if (!Flags.notificationReduceMessagequeueUsage()) {
- mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
- }
+ mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
mHandler.obtainMessage(
MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED,
listenerInterruptionFilter,
@@ -10517,14 +10520,9 @@ public class NotificationManagerService extends SystemService {
}
protected void scheduleSendRankingUpdate() {
- if (Flags.notificationReduceMessagequeueUsage()) {
+ if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
sendMessage(m);
- } else {
- if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
- Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
- sendMessage(m);
- }
}
}
@@ -10533,12 +10531,8 @@ public class NotificationManagerService extends SystemService {
if (lifetimeExtensionRefactor()) {
sendMessageDelayed(Message.obtain(this, cancelRunnable), delay);
} else {
- if (Flags.notificationReduceMessagequeueUsage()) {
+ if (!hasCallbacks(cancelRunnable)) {
sendMessage(Message.obtain(this, cancelRunnable));
- } else {
- if (!hasCallbacks(cancelRunnable)) {
- sendMessage(Message.obtain(this, cancelRunnable));
- }
}
}
}
@@ -10573,9 +10567,7 @@ public class NotificationManagerService extends SystemService {
}
public void requestSort() {
- if (!Flags.notificationReduceMessagequeueUsage()) {
- removeMessages(MESSAGE_RANKING_SORT);
- }
+ removeMessages(MESSAGE_RANKING_SORT);
Message msg = Message.obtain();
msg.what = MESSAGE_RANKING_SORT;
sendMessage(msg);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 45b155049c72..3b34dcd17705 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1962,10 +1962,25 @@ public class PreferencesHelper implements RankingConfig {
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
boolean includeDeleted, boolean includeBundles) {
+ return getNotificationChannels(pkg, uid, includeDeleted, includeBundles, false);
+ }
+
+ protected ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+ boolean includeDeleted, boolean includeBundles, boolean createPrefsIfNeeded) {
+ if (createPrefsIfNeeded && !android.app.Flags.nmBinderPerfCacheChannels()) {
+ Slog.wtf(TAG,
+ "getNotificationChannels called with createPrefsIfNeeded=true and flag off");
+ createPrefsIfNeeded = false;
+ }
Objects.requireNonNull(pkg);
List<NotificationChannel> channels = new ArrayList<>();
synchronized (mLock) {
- PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
+ PackagePreferences r;
+ if (createPrefsIfNeeded) {
+ r = getOrCreatePackagePreferencesLocked(pkg, uid);
+ } else {
+ r = getPackagePreferencesLocked(pkg, uid);
+ }
if (r == null) {
return ParceledListSlice.emptyList();
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index c1ca9c23aef5..b4a8aee66c7c 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -51,13 +51,6 @@ flag {
}
flag {
- name: "notification_reduce_messagequeue_usage"
- namespace: "systemui"
- description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
- bug: "311051285"
-}
-
-flag {
name: "notification_test"
namespace: "systemui"
description: "Timing test, no functionality"
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 3660607d8764..bf0e77e03171 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -86,8 +86,6 @@ import java.util.function.Supplier;
*/
public final class BroadcastHelper {
private static final boolean DEBUG_BROADCASTS = false;
- private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
- "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
private final UserManagerInternal mUmInternal;
private final ActivityManagerInternal mAmInternal;
@@ -398,8 +396,7 @@ public final class BroadcastHelper {
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
broadcastAllowList, "android" /* targetPackageName */,
- new String[]{
- PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+ null /* requiredPermissions */);
}
// Second, send the PACKAGE_CHANGED broadcast to the application itself.
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index f6e518a4fed7..90adb6683496 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -729,10 +729,13 @@ final class DeletePackageHelper {
final String internalPackageName =
snapshot.resolveInternalPackageName(packageName, versionCode);
+ final boolean deleteAllUsers = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0;
+ final int[] users = deleteAllUsers ? mUserManagerInternal.getUserIds() : new int[]{userId};
+
if (!isOrphaned(snapshot, internalPackageName)
&& !allowSilentUninstall
- && !isCallerAllowedToSilentlyUninstall(
- snapshot, callingUid, internalPackageName, userId)) {
+ && !isCallerAllowedToSilentlyUninstall(snapshot, callingUid, internalPackageName,
+ users)) {
mPm.mHandler.post(() -> {
try {
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
@@ -751,8 +754,7 @@ final class DeletePackageHelper {
});
return;
}
- final boolean deleteAllUsers = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0;
- final int[] users = deleteAllUsers ? mUserManagerInternal.getUserIds() : new int[]{userId};
+
if (UserHandle.getUserId(callingUid) != userId || (deleteAllUsers && users.length > 1)) {
mPm.mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -916,16 +918,24 @@ final class DeletePackageHelper {
}
private boolean isCallerAllowedToSilentlyUninstall(@NonNull Computer snapshot, int callingUid,
- String pkgName, int userId) {
+ String pkgName, int[] targetUserIds) {
if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
return true;
}
final int callingUserId = UserHandle.getUserId(callingUid);
+
// If the caller installed the pkgName, then allow it to silently uninstall.
- if (callingUid == snapshot.getPackageUid(
- snapshot.getInstallerPackageName(pkgName, userId), 0, callingUserId)) {
- return true;
+ for (int user : targetUserIds) {
+ try {
+ if (callingUid == snapshot.getPackageUid(
+ snapshot.getInstallerPackageName(pkgName, user), 0, callingUserId)) {
+ return true;
+ }
+ } catch (Exception ignored) {
+ // The app to be uninstalled (`pkgName`) is not installed on this `user`. Continue
+ // looking for the installerPkgName in the next user
+ }
}
// Allow package verifier to silently uninstall.
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index f67f56db3c1e..c5937e5ffb90 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -8,6 +8,12 @@
},
{
"name": "PowerServiceTests_server_power"
+ },
+ {
+ "name": "CtsStatsdAtomHostTestCases_statsdatom_powermanager",
+ "file_patterns": [
+ "(/|^)ThermalManagerService.java"
+ ]
}
],
"postsubmit": [
@@ -22,12 +28,6 @@
},
{
"name": "PowerServiceTests_server_power"
- },
- {
- "name": "CtsStatsdAtomHostTestCases_statsdatom_powermanager",
- "file_patterns": [
- "(/|^)ThermalManagerService.java"
- ]
}
]
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index a19a3422af06..b7b4cc0b6861 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -95,7 +95,6 @@ import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
-import android.view.accessibility.Flags;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -981,16 +980,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
- if (Flags.a11yQsShortcut()) {
- StatusBarManagerService.this.addQsTileToFrontOrEnd(tile, end);
- }
+ StatusBarManagerService.this.addQsTileToFrontOrEnd(tile, end);
}
@Override
public void removeQsTile(ComponentName tile) {
- if (Flags.a11yQsShortcut()) {
- StatusBarManagerService.this.remTile(tile);
- }
+ StatusBarManagerService.this.remTile(tile);
}
};
@@ -1098,19 +1093,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
public void addTile(ComponentName component) {
- if (Flags.a11yQsShortcut()) {
- addQsTileToFrontOrEnd(component, false);
- } else {
- enforceStatusBarOrShell();
- enforceValidCallingUser();
-
- if (mBar != null) {
- try {
- mBar.addQsTile(component);
- } catch (RemoteException ex) {
- }
- }
- }
+ addQsTileToFrontOrEnd(component, false);
}
private void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e52fbbfde49f..a24522a5080d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -482,6 +482,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final String launchedFromPackage; // always the package who started the activity.
@Nullable
final String launchedFromFeatureId; // always the feature in launchedFromPackage
+ @LaunchSourceType
int mLaunchSourceType; // latest launch source type
final Intent intent; // the original intent that generated us
final String shortComponentName; // the short component name of the intent
@@ -2330,6 +2331,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLaunchSourceType = determineLaunchSourceType(launchFromUid, caller);
}
+ @LaunchSourceType
private int determineLaunchSourceType(int launchFromUid, WindowProcessController caller) {
if (launchFromUid == Process.SYSTEM_UID || launchFromUid == Process.ROOT_UID) {
return LAUNCH_SOURCE_TYPE_SYSTEM;
@@ -3229,7 +3231,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return false;
}
// If the user preference respects aspect ratio, then it becomes non-resizable.
- return mAppCompatController.getAppCompatAspectRatioOverrides()
+ return mAppCompatController.getAspectRatioOverrides()
.userPreferenceCompatibleWithNonResizability();
}
@@ -5474,7 +5476,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
boolean canAffectSystemUiFlags() {
- return task != null && task.canAffectSystemUiFlags() && isVisible()
+ final TaskFragment taskFragment = getTaskFragment();
+ return taskFragment != null && taskFragment.canAffectSystemUiFlags()
+ && isVisible()
&& !mWaitForEnteringPinnedMode && !inPinnedWindowingMode();
}
@@ -8469,7 +8473,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
if (!mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio()
- && !mAppCompatController.getAppCompatAspectRatioOverrides()
+ && !mAppCompatController.getAspectRatioOverrides()
.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
@@ -10135,29 +10139,27 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
proto.write(LAST_DROP_INPUT_MODE, mLastDropInputMode);
proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
+ final AppCompatCameraOverrides cameraOverrides =
+ mAppCompatController.getAppCompatCameraOverrides();
proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
- mAppCompatController.getAppCompatCameraOverrides()
- .shouldForceRotateForCameraCompat());
+ cameraOverrides.shouldForceRotateForCameraCompat());
proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
- mAppCompatController.getAppCompatCameraOverrides()
- .shouldRefreshActivityForCameraCompat());
+ cameraOverrides.shouldRefreshActivityForCameraCompat());
proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
- mAppCompatController.getAppCompatCameraOverrides()
- .shouldRefreshActivityViaPauseForCameraCompat());
+ cameraOverrides.shouldRefreshActivityViaPauseForCameraCompat());
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ mAppCompatController.getAspectRatioOverrides();
proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
- mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio());
+ aspectRatioOverrides.shouldOverrideMinAspectRatio());
proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
mAppCompatController.getOrientationOverrides()
.shouldIgnoreOrientationRequestLoop());
proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
mAppCompatController.getResizeOverrides().shouldOverrideForceResizeApp());
proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
- mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldEnableUserAspectRatioSettings());
+ aspectRatioOverrides.shouldEnableUserAspectRatioSettings());
proto.write(IS_USER_FULLSCREEN_OVERRIDE_ENABLED,
- mAppCompatController.getAppCompatAspectRatioOverrides()
- .isUserFullscreenOverrideEnabled());
+ aspectRatioOverrides.isUserFullscreenOverrideEnabled());
}
@Override
@@ -10386,7 +10388,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* game engines wait to get focus before drawing the content of the app.
*/
boolean shouldSendCompatFakeFocus() {
- return mAppCompatController.getAppCompatFocusOverrides().shouldSendFakeFocus();
+ return mAppCompatController.getFocusOverrides().shouldSendFakeFocus();
}
boolean canCaptureSnapshot() {
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 26b7cc67876e..21628341ea62 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -100,19 +100,21 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
ActivitySnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
super(service);
- mSnapshotPersistQueue = persistQueue;
- mPersistInfoProvider = createPersistInfoProvider(service,
- Environment::getDataSystemCeDirectory);
- mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
- mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider);
- initialize(new ActivitySnapshotCache());
-
final boolean snapshotEnabled =
!service.mContext
.getResources()
.getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots)
&& !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go
setSnapshotEnabled(snapshotEnabled);
+ mSnapshotPersistQueue = persistQueue;
+ mPersistInfoProvider = createPersistInfoProvider(service,
+ Environment::getDataSystemCeDirectory);
+ mPersister = new TaskSnapshotPersister(
+ persistQueue,
+ mPersistInfoProvider,
+ shouldDisableSnapshots());
+ mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider);
+ initialize(new ActivitySnapshotCache());
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 3e232ea6b612..4ecd0bec9880 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -76,7 +76,7 @@ class AppCompatAspectRatioPolicy {
private float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
@NonNull Rect parentBounds) {
final float letterboxAspectRatioOverride =
- mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+ mAppCompatOverrides.getAspectRatioOverrides()
.getFixedOrientationLetterboxAspectRatio(newParentConfig);
// Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
// be respected in #applyAspectRatio.
@@ -127,7 +127,7 @@ class AppCompatAspectRatioPolicy {
}
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+ mAppCompatOverrides.getAspectRatioOverrides();
if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
return aspectRatioOverrides.getUserMinAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index fa510dbcafae..a94f6252cd68 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -35,7 +35,7 @@ class AppCompatController {
@NonNull
private final AppCompatReachabilityPolicy mReachabilityPolicy;
@NonNull
- private final DesktopAppCompatAspectRatioPolicy mDesktopAppCompatAspectRatioPolicy;
+ private final DesktopAppCompatAspectRatioPolicy mDesktopAspectRatioPolicy;
@NonNull
private final AppCompatOverrides mAppCompatOverrides;
@NonNull
@@ -62,7 +62,7 @@ class AppCompatController {
wmService.mAppCompatConfiguration);
mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord,
wmService.mAppCompatConfiguration);
- mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
+ mDesktopAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord,
mAppCompatOverrides);
@@ -84,8 +84,8 @@ class AppCompatController {
}
@NonNull
- DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() {
- return mDesktopAppCompatAspectRatioPolicy;
+ DesktopAppCompatAspectRatioPolicy getDesktopAspectRatioPolicy() {
+ return mDesktopAspectRatioPolicy;
}
@NonNull
@@ -99,8 +99,8 @@ class AppCompatController {
}
@NonNull
- AppCompatAspectRatioOverrides getAppCompatAspectRatioOverrides() {
- return mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+ AppCompatAspectRatioOverrides getAspectRatioOverrides() {
+ return mAppCompatOverrides.getAspectRatioOverrides();
}
@NonNull
@@ -119,8 +119,8 @@ class AppCompatController {
}
@NonNull
- AppCompatFocusOverrides getAppCompatFocusOverrides() {
- return mAppCompatOverrides.getAppCompatFocusOverrides();
+ AppCompatFocusOverrides getFocusOverrides() {
+ return mAppCompatOverrides.getFocusOverrides();
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index fc758ef90995..6202f8070dd4 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -55,7 +55,7 @@ class AppCompatOrientationPolicy {
@ActivityInfo.ScreenOrientation
int overrideOrientationIfNeeded(@ActivityInfo.ScreenOrientation int candidate) {
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+ mAppCompatOverrides.getAspectRatioOverrides();
// Ignore all orientation requests of activities for eligible virtual displays.
if (aspectRatioOverrides.shouldIgnoreActivitySizeRestrictionsForDisplay()) {
return SCREEN_ORIENTATION_USER;
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 9fb54db23d55..2d0ff9be2133 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -31,9 +31,9 @@ public class AppCompatOverrides {
@NonNull
private final AppCompatCameraOverrides mAppCompatCameraOverrides;
@NonNull
- private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides;
+ private final AppCompatAspectRatioOverrides mAspectRatioOverrides;
@NonNull
- private final AppCompatFocusOverrides mAppCompatFocusOverrides;
+ private final AppCompatFocusOverrides mFocusOverrides;
@NonNull
private final AppCompatResizeOverrides mResizeOverrides;
@NonNull
@@ -52,11 +52,11 @@ public class AppCompatOverrides {
appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
appCompatConfiguration, appCompatDeviceStateQuery);
- mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
+ mAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
appCompatConfiguration, optPropBuilder, appCompatDeviceStateQuery,
mReachabilityOverrides);
- mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
- appCompatConfiguration, optPropBuilder);
+ mFocusOverrides = new AppCompatFocusOverrides(activityRecord, appCompatConfiguration,
+ optPropBuilder);
mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager,
optPropBuilder);
mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord,
@@ -74,13 +74,13 @@ public class AppCompatOverrides {
}
@NonNull
- AppCompatAspectRatioOverrides getAppCompatAspectRatioOverrides() {
- return mAppCompatAspectRatioOverrides;
+ AppCompatAspectRatioOverrides getAspectRatioOverrides() {
+ return mAspectRatioOverrides;
}
@NonNull
- AppCompatFocusOverrides getAppCompatFocusOverrides() {
- return mAppCompatFocusOverrides;
+ AppCompatFocusOverrides getFocusOverrides() {
+ return mFocusOverrides;
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index 98bb8e79b51f..f48ef4fa8f5f 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -414,7 +414,7 @@ class AppCompatSizeCompatModePolicy {
* aspect ratio.
*/
boolean shouldCreateAppCompatDisplayInsets() {
- if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioOverrides()
+ if (mActivityRecord.mAppCompatController.getAspectRatioOverrides()
.hasFullscreenOverride()) {
// If the user has forced the applications aspect ratio to be fullscreen, don't use size
// compatibility mode in any situation. The user has been warned and therefore accepts
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index e54e93abfcf4..1ab0868b37d1 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -156,7 +156,7 @@ final class AppCompatUtils {
.getAppCompatLetterboxOverrides().isLetterboxEducationEnabled());
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- top.mAppCompatController.getAppCompatAspectRatioOverrides();
+ top.mAppCompatController.getAspectRatioOverrides();
appCompatTaskInfo.setUserFullscreenOverrideEnabled(
aspectRatioOverrides.shouldApplyUserFullscreenOverride());
appCompatTaskInfo.setSystemFullscreenOverrideEnabled(
@@ -206,7 +206,7 @@ final class AppCompatUtils {
appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
AppCompatCameraPolicy.getCameraCompatFreeformMode(top);
appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
- .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(task));
+ .getDesktopAspectRatioPolicy().hasMinAspectRatioOverride(task));
}
/**
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 43855aa3d247..fee5566af484 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -189,7 +189,7 @@ public class DesktopAppCompatAspectRatioPolicy {
final ActivityInfo info = mActivityRecord.info;
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+ mAppCompatOverrides.getAspectRatioOverrides();
if (shouldApplyUserMinAspectRatioOverride(task)) {
return aspectRatioOverrides.getUserMinAspectRatio();
}
@@ -266,7 +266,7 @@ public class DesktopAppCompatAspectRatioPolicy {
return false;
}
- final int userAspectRatioCode = mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+ final int userAspectRatioCode = mAppCompatOverrides.getAspectRatioOverrides()
.getUserMinAspectRatioOverrideCode();
return userAspectRatioCode != USER_MIN_ASPECT_RATIO_UNSET
@@ -281,7 +281,7 @@ public class DesktopAppCompatAspectRatioPolicy {
// We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has
// effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null,
// the current app doesn't opt-out so the first part of the predicate is true.
- return mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+ return mAppCompatOverrides.getAspectRatioOverrides()
.getAllowUserAspectRatioOverridePropertyValue()
&& mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled()
&& task.mDisplayContent.getIgnoreOrientationRequest();
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index fcf88d395f1c..0106a64660fe 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -104,7 +104,7 @@ public final class DesktopModeBoundsCalculator {
if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) {
return centerInScreen(idealSize, screenBounds);
}
- if (activity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ if (activity.mAppCompatController.getAspectRatioOverrides()
.hasFullscreenOverride()) {
// If the activity has a fullscreen override applied, it should be treated as
// resizeable and match the device orientation. Thus the ideal size can be
@@ -112,7 +112,7 @@ public final class DesktopModeBoundsCalculator {
return centerInScreen(idealSize, screenBounds);
}
final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
- activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+ activity.mAppCompatController.getDesktopAspectRatioPolicy();
float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task);
final float tdaWidth = stableBounds.width();
final float tdaHeight = stableBounds.height();
@@ -190,7 +190,7 @@ public final class DesktopModeBoundsCalculator {
@NonNull ActivityRecord activity, @NonNull Task task) {
final int activityOrientation = activity.getOverrideOrientation();
final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
- activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+ activity.mAppCompatController.getDesktopAspectRatioPolicy();
if (desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task)
&& (!isFixedOrientation(activityOrientation)
|| activityOrientation == SCREEN_ORIENTATION_LOCKED)) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d32c31f1c1c7..5435d8f164da 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -193,7 +193,6 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -2187,12 +2186,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- /** Returns {@code true} if the screen rotation animation needs to wait for the window. */
- boolean shouldSyncRotationChange(WindowState w) {
- final AsyncRotationController controller = mAsyncRotationController;
- return controller == null || !controller.isAsync(w);
- }
-
void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
if (mFixedRotationLaunchingApp != null) {
// The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
@@ -2279,10 +2272,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (!shellTransitions) {
forAllWindows(w -> {
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
- if (!rotateSeamlessly && w.mHasSurface) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Set mOrientationChanging of %s", w);
- w.setOrientationChanging(true);
- }
}, true /* traverseTopToBottom */);
mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
@@ -5083,15 +5072,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
Slog.w(TAG_WM, "Window freeze timeout expired.");
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
- forAllWindows(w -> {
- if (!w.getOrientationChanging()) {
- return;
- }
- w.orientationChangeTimedOut();
- w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- - mWmService.mDisplayFreezeTime);
- Slog.w(TAG_WM, "Force clearing orientation change: " + w);
- }, true /* traverseTopToBottom */);
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index db058cafe5fe..fa748d3a22a5 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -212,7 +212,7 @@ class DragDropController {
surface = null;
mDragState.mPid = callerPid;
mDragState.mUid = callerUid;
- mDragState.mOriginalAlpha = alpha;
+ mDragState.mStartDragAlpha = alpha;
mDragState.mAnimatedScale = callingWin.mGlobalScale;
mDragState.mToken = dragToken;
mDragState.mStartDragDisplayContent = displayContent;
@@ -287,7 +287,7 @@ class DragDropController {
}
final SurfaceControl.Transaction transaction = mDragState.mTransaction;
- transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
+ transaction.setAlpha(surfaceControl, mDragState.mStartDragAlpha);
transaction.show(surfaceControl);
displayContent.reparentToOverlay(transaction, surfaceControl);
mDragState.updateDragSurfaceLocked(true /* keepHandling */,
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index d48b9b4a5d10..69f32cb7b8ea 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -82,6 +82,7 @@ import java.util.concurrent.CompletableFuture;
class DragState {
private static final long MIN_ANIMATION_DURATION_MS = 195;
private static final long MAX_ANIMATION_DURATION_MS = 375;
+ private static final float DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE = 0.75f;
private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
View.DRAG_FLAG_GLOBAL_URI_WRITE;
@@ -114,8 +115,9 @@ class DragState {
boolean mDragResult;
boolean mRelinquishDragSurfaceToDropTarget;
float mAnimatedScale = 1.0f;
- float mOriginalAlpha;
- float mOriginalDisplayX, mOriginalDisplayY;
+ float mStartDragAlpha;
+ // Coords are in display coordinates space.
+ float mStartDragDisplayX, mStartDragDisplayY;
float mCurrentDisplayX, mCurrentDisplayY;
float mThumbOffsetX, mThumbOffsetY;
InputInterceptor mInputInterceptor;
@@ -497,8 +499,8 @@ class DragState {
*/
void broadcastDragStartedLocked(final float touchX, final float touchY) {
Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_STARTED");
- mOriginalDisplayX = mCurrentDisplayX = touchX;
- mOriginalDisplayY = mCurrentDisplayY = touchY;
+ mStartDragDisplayX = mCurrentDisplayX = touchX;
+ mStartDragDisplayY = mCurrentDisplayY = touchY;
// Cache a base-class instance of the clip metadata so that parceling
// works correctly in calling out to the apps.
@@ -809,21 +811,32 @@ class DragState {
mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
+ duration = MIN_ANIMATION_DURATION_MS;
+ } else if (Flags.enableConnectedDisplaysDnd() && mCurrentDisplayContent.getDisplayId()
+ != mStartDragDisplayContent.getDisplayId()) {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
+ mCurrentDisplayX - mThumbOffsetX, mCurrentDisplayX - mThumbOffsetX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
+ mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY - mThumbOffsetY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+ DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE * mAnimatedScale),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
duration = MIN_ANIMATION_DURATION_MS;
} else {
animator = ValueAnimator.ofPropertyValuesHolder(
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
- mCurrentDisplayX - mThumbOffsetX, mOriginalDisplayX - mThumbOffsetX),
+ mCurrentDisplayX - mThumbOffsetX, mStartDragDisplayX - mThumbOffsetX),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
- mCurrentDisplayY - mThumbOffsetY, mOriginalDisplayY - mThumbOffsetY),
+ mCurrentDisplayY - mThumbOffsetY, mStartDragDisplayY - mThumbOffsetY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha,
- mOriginalAlpha / 2));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha,
+ mStartDragAlpha / 2));
- final float translateX = mOriginalDisplayX - mCurrentDisplayX;
- final float translateY = mOriginalDisplayY - mCurrentDisplayY;
+ final float translateX = mStartDragDisplayX - mCurrentDisplayX;
+ final float translateY = mStartDragDisplayY - mCurrentDisplayY;
// Adjust the duration to the travel distance.
final double travelDistance = Math.sqrt(
translateX * translateX + translateY * translateY);
@@ -853,7 +866,7 @@ class DragState {
mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
} else {
animator = ValueAnimator.ofPropertyValuesHolder(
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
@@ -861,7 +874,7 @@ class DragState {
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0));
}
final AnimationListener listener = new AnimationListener();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 04f09d5fe627..7a3eb67bf94e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -205,7 +205,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// For seamless rotation cases this always stays true, as the windows complete their orientation
// changes 1 by 1 without disturbing global state.
boolean mOrientationChangeComplete = true;
- boolean mWallpaperActionPending = false;
private final Handler mHandler;
@@ -1100,10 +1099,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
}
- if ((bulkUpdateParams & SET_WALLPAPER_ACTION_PENDING) != 0) {
- mWallpaperActionPending = true;
- }
-
return doRequest;
}
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 3eb13c52cca6..5e1d7928e96d 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -231,8 +231,13 @@ class SnapshotPersistQueue {
if (next.isReady(mUserManagerInternal)) {
isReadyToWrite = true;
next.onDequeuedLocked();
- } else {
+ } else if (!mShutdown) {
mWriteQueue.addLast(next);
+ } else {
+ // User manager is locked and device is shutting down, skip writing
+ // this item.
+ next.onDequeuedLocked();
+ next = null;
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fe478c60bc32..295759c2fc7e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -468,9 +468,6 @@ class Task extends TaskFragment {
// NOTE: This value needs to be persisted with each task
private TaskDescription mTaskDescription;
- /** @see #setCanAffectSystemUiFlags */
- private boolean mCanAffectSystemUiFlags = true;
-
private static Exception sTmpException;
private boolean mForceShowForAllUsers;
@@ -3288,21 +3285,6 @@ class Task extends TaskFragment {
return isRootTask() && callback.test(this) ? this : null;
}
- /**
- * @param canAffectSystemUiFlags If false, all windows in this task can not affect SystemUI
- * flags. See {@link WindowState#canAffectSystemUiFlags()}.
- */
- void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) {
- mCanAffectSystemUiFlags = canAffectSystemUiFlags;
- }
-
- /**
- * @see #setCanAffectSystemUiFlags
- */
- boolean canAffectSystemUiFlags() {
- return mCanAffectSystemUiFlags;
- }
-
void dontAnimateDimExit() {
mDimmer.dontAnimateExit();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a031acad638f..1993053c16cd 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -409,6 +409,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
private boolean mForceTranslucent = false;
+ /** @see #setCanAffectSystemUiFlags */
+ private boolean mCanAffectSystemUiFlags = true;
+
final Point mLastSurfaceSize = new Point();
private final Rect mTmpBounds = new Rect();
@@ -967,6 +970,27 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
/**
+ * @param canAffectSystemUiFlags If false, all windows in this taskfragment can not affect
+ * SystemUI flags. See
+ * {@link WindowState#canAffectSystemUiFlags()}.
+ */
+ void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) {
+ mCanAffectSystemUiFlags = canAffectSystemUiFlags;
+ }
+
+ /**
+ * @see #setCanAffectSystemUiFlags
+ */
+ boolean canAffectSystemUiFlags() {
+ if (!mCanAffectSystemUiFlags) {
+ return false;
+ }
+ final TaskFragment parentTaskFragment =
+ getParent() != null ? getParent().asTaskFragment() : null;
+ return parentTaskFragment == null || parentTaskFragment.canAffectSystemUiFlags();
+ }
+
+ /**
* Returns the TaskFragment that is being organized, which could be this or the ascendant
* TaskFragment.
*/
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index d89dc0b8e81c..91cd9498a356 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -245,18 +245,20 @@ public class TaskPersister implements PersisterQueue.Listener {
private static String fileToString(File file) {
final String newline = System.lineSeparator();
+ BufferedReader reader = null;
try {
- BufferedReader reader = new BufferedReader(new FileReader(file));
+ reader = new BufferedReader(new FileReader(file));
StringBuffer sb = new StringBuffer((int) file.length() * 2);
String line;
while ((line = reader.readLine()) != null) {
sb.append(line + newline);
}
- reader.close();
return sb.toString();
} catch (IOException ioe) {
Slog.e(TAG, "Couldn't read file " + file.getName());
return null;
+ } finally {
+ IoUtils.closeQuietly(reader);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 7d300e98f44b..432ed1d0b61d 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -66,16 +66,19 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
super(service);
- mPersistInfoProvider = createPersistInfoProvider(service,
- Environment::getDataSystemCeDirectory);
- mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
-
- initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider)));
final boolean snapshotEnabled =
!service.mContext
.getResources()
.getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots);
setSnapshotEnabled(snapshotEnabled);
+ mPersistInfoProvider = createPersistInfoProvider(service,
+ Environment::getDataSystemCeDirectory);
+
+ mPersister = new TaskSnapshotPersister(
+ persistQueue,
+ mPersistInfoProvider,
+ shouldDisableSnapshots());
+ initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider)));
}
static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 87be74ae1dd9..538fd8dc5406 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -26,6 +26,7 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.UserManagerInternal;
+import com.android.window.flags.Flags;
import java.io.File;
import java.util.Arrays;
@@ -37,6 +38,8 @@ import java.util.Arrays;
*/
class TaskSnapshotPersister extends BaseAppSnapshotPersister {
+ private final boolean mDisableSnapshots;
+
/**
* The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
* called.
@@ -45,8 +48,10 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
TaskSnapshotPersister(SnapshotPersistQueue persistQueue,
- PersistInfoProvider persistInfoProvider) {
+ PersistInfoProvider persistInfoProvider,
+ boolean disableSnapshots) {
super(persistQueue, persistInfoProvider);
+ mDisableSnapshots = Flags.checkDisabledSnapshotsInTaskPersister() && disableSnapshots;
}
/**
@@ -57,6 +62,9 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
* @param snapshot The snapshot to persist.
*/
void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
+ if (mDisableSnapshots) {
+ return;
+ }
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
super.persistSnapshot(taskId, userId, snapshot);
@@ -71,6 +79,9 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
*/
@Override
void removeSnapshot(int taskId, int userId) {
+ if (mDisableSnapshots) {
+ return;
+ }
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
super.removeSnapshot(taskId, userId);
@@ -86,7 +97,7 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
* model.
*/
void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
- if (runningUserIds.length == 0) {
+ if (runningUserIds.length == 0 || mDisableSnapshots) {
return;
}
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dd6e15b74a58..bf23e757fee5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6407,11 +6407,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
&& mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
- // WindowsState#reportResized won't tell invisible requested window to redraw,
- // so do not set it as changing orientation to avoid affecting draw state.
- if (w.isVisibleRequested()) {
- w.setOrientationChanging(true);
- }
if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
// XXX should probably keep timeout from
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index f0f1b2e47cc8..3c3a180f4da1 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -38,6 +38,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_
import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
@@ -1862,6 +1863,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
taskFragment.setPinned(pinned);
break;
}
+ case OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS: {
+ taskFragment.setCanAffectSystemUiFlags(operation.getBooleanValue());
+
+ // Request to apply the flags.
+ mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
+ break;
+ }
}
return effects;
}
@@ -1937,6 +1945,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return false;
}
+ if ((opType == OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS)
+ && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+ final Throwable exception = new SecurityException(
+ "Only a system organizer can perform OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS."
+ );
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ opType, exception);
+ return false;
+ }
+
final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
return secondaryFragmentToken == null
|| validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 85e3d89730a3..da58470edc1e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -148,7 +148,6 @@ import static com.android.server.wm.WindowManagerService.MY_PID;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
@@ -592,27 +591,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/** Completely remove from window manager after exit animation? */
boolean mRemoveOnExit;
- /**
- * Set when the orientation is changing and this window has not yet
- * been updated for the new orientation.
- */
- private boolean mOrientationChanging;
-
/** The time when the window was last requested to redraw for orientation change. */
private long mOrientationChangeRedrawRequestTime;
/**
- * Sometimes in addition to the mOrientationChanging
- * flag we report that the orientation is changing
- * due to a mismatch in current and reported configuration.
- *
- * In the case of timeout we still need to make sure we
- * leave the orientation changing state though, so we
- * use this as a special time out escape hatch.
- */
- private boolean mOrientationChangeTimedOut;
-
- /**
* The orientation during the last visible call to relayout. If our
* current orientation is different, the window can't be ready
* to be shown.
@@ -1497,8 +1479,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Reset the drawn state if the window need to redraw for the change, so the transition
// can wait until it has finished drawing to start.
- if ((configChanged || getOrientationChanging() || dragResizingChanged)
- && isVisibleRequested()) {
+ if ((configChanged || dragResizingChanged) && isVisibleRequested()) {
winAnimator.mDrawState = DRAW_PENDING;
if (mActivityRecord != null) {
mActivityRecord.clearAllDrawn();
@@ -1512,15 +1493,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
ProtoLog.v(WM_DEBUG_RESIZE, "Resizing window %s", this);
mWmService.mResizingWindows.add(this);
}
- } else if (getOrientationChanging()) {
- if (isDrawn()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Orientation not waiting for draw in %s, surfaceController %s", this,
- winAnimator.mSurfaceControl);
- setOrientationChanging(false);
- mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- - mWmService.mDisplayFreezeTime);
- }
}
}
@@ -1528,46 +1500,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return !mWindowFrames.mFrame.equals(mWindowFrames.mLastFrame);
}
- boolean getOrientationChanging() {
- if (mTransitionController.isShellTransitionsEnabled()) {
- // Shell transition doesn't use the methods for display frozen state.
- return false;
- }
- // In addition to the local state flag, we must also consider the difference in the last
- // reported configuration vs. the current state. If the client code has not been informed of
- // the change, logic dependent on having finished processing the orientation, such as
- // unfreezing, could be improperly triggered.
- // TODO(b/62846907): Checking against {@link mLastReportedConfiguration} could be flaky as
- // this is not necessarily what the client has processed yet. Find a
- // better indicator consistent with the client.
- return (mOrientationChanging || (isVisible()
- && getConfiguration().orientation != getLastReportedConfiguration().orientation))
- && !mSeamlesslyRotated
- && !mOrientationChangeTimedOut;
- }
-
- void setOrientationChanging(boolean changing) {
- mOrientationChangeTimedOut = false;
- if (mOrientationChanging == changing) {
- return;
- }
- mOrientationChanging = changing;
- if (changing) {
- mLastFreezeDuration = 0;
- if (mWmService.mRoot.mOrientationChangeComplete
- && mDisplayContent.shouldSyncRotationChange(this)) {
- mWmService.mRoot.mOrientationChangeComplete = false;
- }
- } else {
- // The orientation change is completed. If it was hidden by the animation, reshow it.
- mDisplayContent.finishAsyncRotation(mToken);
- }
- }
-
- void orientationChangeTimedOut() {
- mOrientationChangeTimedOut = true;
- }
-
@Override
void onDisplayChanged(DisplayContent dc) {
if (dc != null && mDisplayContent != null && dc != mDisplayContent
@@ -3355,12 +3287,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mAppFreezing = false;
- if (mHasSurface && !getOrientationChanging()
- && mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "set mOrientationChanging of %s", this);
- setOrientationChanging(true);
- }
mLastFreezeDuration = 0;
setDisplayLayoutNeeded();
return true;
@@ -4266,9 +4192,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
- if (getOrientationChanging() || mAppFreezing) {
- pw.println(prefix + "mOrientationChanging=" + mOrientationChanging
- + " configOrientationChanging="
+ if (mAppFreezing) {
+ pw.println(prefix + " configOrientationChanging="
+ (getLastReportedConfiguration().orientation != getConfiguration().orientation)
+ " mAppFreezing=" + mAppFreezing);
}
@@ -5356,7 +5281,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Send information to SurfaceFlinger about the priority of the current window.
updateFrameRateSelectionPriorityIfNeeded();
updateScaleIfNeeded();
- mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+ mWinAnimator.prepareSurfaceLocked(getPendingTransaction());
applyDims();
}
super.prepareSurfaces();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d973fb014e35..298580e4bb81 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -29,7 +29,6 @@ import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DRAW;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
@@ -417,56 +416,19 @@ class WindowStateAnimator {
}
}
- void computeShownFrameLocked() {
- if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
- return;
- } else if (mWin.isDragResizeChanged()) {
- // This window is awaiting a relayout because user just started (or ended)
- // drag-resizing. The shown frame (which affects surface size and pos)
- // should not be updated until we get next finished draw with the new surface.
- // Otherwise one or two frames rendered with old settings would be displayed
- // with new geometry.
- return;
- }
-
- if (DEBUG) {
- Slog.v(TAG, "computeShownFrameLocked: " + this
- + " not attached, mAlpha=" + mAlpha);
- }
-
- mShownAlpha = mAlpha;
- }
-
void prepareSurfaceLocked(SurfaceControl.Transaction t) {
final WindowState w = mWin;
if (!hasSurface()) {
-
- // There is no need to wait for an animation change if our window is gone for layout
- // already as we'll never be visible.
- if (w.getOrientationChanging() && w.isGoneForLayout()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change skips hidden %s", w);
- w.setOrientationChanging(false);
- }
return;
}
- computeShownFrameLocked();
+ mShownAlpha = mAlpha;
if (!w.isOnScreen()) {
hide(t, "prepareSurfaceLocked");
if (!w.mIsWallpaper || !mService.mFlags.mEnsureWallpaperInTransitions) {
mWallpaperControllerLocked.hideWallpapers(w);
}
-
- // If we are waiting for this window to handle an orientation change. If this window is
- // really hidden (gone for layout), there is no point in still waiting for it.
- // Note that this does introduce a potential glitch if the window becomes unhidden
- // before it has drawn for the new orientation.
- if (w.getOrientationChanging() && w.isGoneForLayout()) {
- w.setOrientationChanging(false);
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Orientation change skips hidden %s", w);
- }
} else if (mLastAlpha != mShownAlpha
|| mLastHidden) {
mLastAlpha = mShownAlpha;
@@ -483,20 +445,6 @@ class WindowStateAnimator {
}
}
}
-
- if (w.getOrientationChanging()) {
- if (!w.isDrawn()) {
- if (w.mDisplayContent.shouldSyncRotationChange(w)) {
- w.mWmService.mRoot.mOrientationChangeComplete = false;
- mAnimator.mLastWindowFreezeSource = w;
- }
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Orientation continue waiting for draw in %s", w);
- } else {
- w.setOrientationChanging(false);
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change complete in %s", w);
- }
- }
}
private void showRobustly(SurfaceControl.Transaction t) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index dff718a4b7d5..a34b5115faf9 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -127,7 +127,6 @@ class WindowSurfacePlacer {
mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
loopCount--;
} while (mTraversalScheduled && loopCount > 0);
- mService.mRoot.mWallpaperActionPending = false;
}
private void performSurfacePlacementLoop() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0ce25db6ea55..e960abd3b313 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16412,7 +16412,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public android.app.admin.EnforcingAdmin getEnforcingAdmin(int userId, String identifier) {
- Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+ Preconditions.checkCallAuthorization(canQueryAdminPolicy(getCallerIdentity()));
return getEnforcingAdminInternal(userId, identifier);
}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index ce4126a425c6..55d23585fb70 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -34,8 +34,10 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.os.PowerManager;
import android.util.ArraySet;
import android.util.Dumpable;
+import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -44,6 +46,7 @@ import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScr
import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription;
+import com.android.server.policy.feature.flags.FeatureFlags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -62,12 +65,19 @@ import java.util.function.Supplier;
public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>,
DisplayManager.DisplayListener, Dumpable {
+ private static final String TAG = "BookStyleClosedStatePredicate";
+
private final BookStylePreferredScreenCalculator mClosedStateCalculator;
private final Handler mHandler = new Handler();
private final PostureEstimator mPostureEstimator;
private final DisplayManager mDisplayManager;
+ private final PowerManager mPowerManager;
+ private final FeatureFlags mFeatureFlags;
private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
+ @PowerManager.ScreenTimeoutPolicy
+ private volatile int mScreenTimeoutPolicy;
+
/**
* Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair
* of accelerometer sensors (one for each movable part of the device), see parameter
@@ -92,8 +102,11 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
public BookStyleClosedStatePredicate(@NonNull Context context,
@NonNull ClosedStateUpdatesListener updatesListener,
@Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
- @NonNull List<StateTransition> stateTransitions) {
+ @NonNull List<StateTransition> stateTransitions,
+ @NonNull FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
mDisplayManager = context.getSystemService(DisplayManager.class);
+ mPowerManager = context.getSystemService(PowerManager.class);
mDisplayManager.registerDisplayListener(this, mHandler);
mClosedStateCalculator = new BookStylePreferredScreenCalculator(stateTransitions);
@@ -108,6 +121,23 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
}
/**
+ * Initialize the predicate, the predicate could subscribe to various data sources the data
+ * from which could be used later when calling {@link BookStyleClosedStatePredicate#test}.
+ */
+ public void init() {
+ if (mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
+ try {
+ mPowerManager.addScreenTimeoutPolicyListener(DEFAULT_DISPLAY, Runnable::run,
+ new ScreenTimeoutPolicyListener());
+ } catch (IllegalStateException exception) {
+ // TODO: b/389613319 - remove after removing the screen timeout policy API flagging
+ Slog.e(TAG, "Error subscribing to the screen timeout policy changes");
+ exception.printStackTrace();
+ }
+ }
+ }
+
+ /**
* Based on the current sensor readings and current state, returns true if the device should use
* 'CLOSED' device state and false if it should not use 'CLOSED' state (e.g. could use half-open
* or open states).
@@ -119,13 +149,24 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0);
+ final boolean isLikelyTentOrWedgeMode = mPostureEstimator.isLikelyTentOrWedgeMode()
+ || shouldForceTentOrWedgeMode();
+
final PreferredScreen preferredScreen = mClosedStateCalculator.
- calculatePreferredScreen(hingeAngle, mPostureEstimator.isLikelyTentOrWedgeMode(),
+ calculatePreferredScreen(hingeAngle, isLikelyTentOrWedgeMode,
mPostureEstimator.isLikelyReverseWedgeMode(hingeAngle));
return preferredScreen == OUTER;
}
+ private boolean shouldForceTentOrWedgeMode() {
+ if (!mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
+ return false;
+ }
+
+ return mScreenTimeoutPolicy == PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON;
+ }
+
private HingeAngle hingeAngleFromFloat(float hingeAngle) {
if (hingeAngle == 0f) {
return ANGLE_0;
@@ -163,7 +204,7 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
@Override
public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
writer.println(" " + getDumpableName());
-
+ writer.println(" mScreenTimeoutPolicy=" + mScreenTimeoutPolicy);
mPostureEstimator.dump(writer, args);
mClosedStateCalculator.dump(writer, args);
}
@@ -172,6 +213,15 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
void onClosedStateUpdated();
}
+ private class ScreenTimeoutPolicyListener implements
+ PowerManager.ScreenTimeoutPolicyListener {
+ @Override
+ public void onScreenTimeoutPolicyChanged(int screenTimeoutPolicy) {
+ // called from the binder thread
+ mScreenTimeoutPolicy = screenTimeoutPolicy;
+ }
+ }
+
/**
* Estimates if the device is going to enter wedge/tent mode based on the sensor data
*/
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index f34ec72d7e27..dfc4ba2cfa71 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -114,7 +114,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
final DeviceStatePredicateWrapper[] configuration = createConfiguration(
- leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees);
+ leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees, featureFlags);
mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor,
hallSensor, displayManager, configuration);
@@ -122,10 +122,10 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
private DeviceStatePredicateWrapper[] createConfiguration(
@Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
- Integer closeAngleDegrees) {
+ Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) {
return new DeviceStatePredicateWrapper[]{
createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor,
- closeAngleDegrees),
+ closeAngleDegrees, featureFlags),
createConfig(getHalfOpenedDeviceState(), /* activeStatePredicate= */
(provider) -> {
final float hingeAngle = provider.getHingeAngle();
@@ -147,7 +147,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
private DeviceStatePredicateWrapper createClosedConfiguration(
@Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
- @Nullable Integer closeAngleDegrees) {
+ @Nullable Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) {
if (closeAngleDegrees != null) {
// Switch displays at closeAngleDegrees in both ways (folding and unfolding)
@@ -161,9 +161,12 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
if (mEnablePostureBasedClosedState) {
// Use smart closed state predicate that will use different switch angles
// based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode)
- return createConfig(getClosedDeviceState(), /* activeStatePredicate= */
- new BookStyleClosedStatePredicate(mContext, this, leftAccelerometerSensor,
- rightAccelerometerSensor, DEFAULT_STATE_TRANSITIONS));
+ final BookStyleClosedStatePredicate predicate = new BookStyleClosedStatePredicate(
+ mContext, this, leftAccelerometerSensor, rightAccelerometerSensor,
+ DEFAULT_STATE_TRANSITIONS, featureFlags);
+ return createConfig(getClosedDeviceState(),
+ /* activeStatePredicate= */ predicate,
+ /* initializer= */ predicate::init);
}
// Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index daeaa9833d78..8e749529978e 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -200,6 +200,16 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
}
}
+ @Override
+ public void onSystemReady() {
+ for (int i = 0; i < mConfigurations.length; i++) {
+ final DeviceStatePredicateWrapper configuration = mConfigurations[i];
+ if (configuration.mInitializer != null) {
+ configuration.mInitializer.run();
+ }
+ }
+ }
+
private void assertUniqueDeviceStateIdentifier(DeviceStatePredicateWrapper configuration) {
if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
throw new IllegalArgumentException("Device state configurations must have unique"
@@ -461,11 +471,12 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
private final DeviceState mDeviceState;
private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate;
private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate;
+ private final Runnable mInitializer;
private DeviceStatePredicateWrapper(
@NonNull DeviceState deviceState,
@NonNull Predicate<FoldableDeviceStateProvider> predicate) {
- this(deviceState, predicate, ALLOWED);
+ this(deviceState, predicate, ALLOWED, /* initializer= */ null);
}
/** Create a configuration with availability and availability predicate **/
@@ -473,10 +484,28 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
@NonNull DeviceState deviceState,
@NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
@NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) {
+ this(deviceState, activeStatePredicate, availabilityPredicate, /* initializer= */ null);
+ }
+
+ /**
+ * Create a configuration with availability and availability predicate.
+ * @param deviceState specifies device state for this configuration
+ * @param activeStatePredicate predicate that should return 'true' when this device state
+ * wants to be and can be active
+ * @param availabilityPredicate predicate that should return 'true' only when this device
+ * state is allowed
+ * @param initializer callback that will be called when the system is booted and ready
+ */
+ private DeviceStatePredicateWrapper(
+ @NonNull DeviceState deviceState,
+ @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
+ @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate,
+ @Nullable Runnable initializer) {
mDeviceState = deviceState;
mActiveStatePredicate = activeStatePredicate;
mAvailabilityPredicate = availabilityPredicate;
+ mInitializer = initializer;
}
/** Create a configuration with an active state predicate **/
@@ -487,6 +516,16 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate);
}
+ /** Create a configuration with an active state predicate and an initializer **/
+ public static DeviceStatePredicateWrapper createConfig(
+ @NonNull DeviceState deviceState,
+ @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
+ @Nullable Runnable initializer
+ ) {
+ return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate, ALLOWED,
+ initializer);
+ }
+
/** Create a configuration with availability and active state predicate **/
public static DeviceStatePredicateWrapper createConfig(
@NonNull DeviceState deviceState,
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 21e33dd1b99a..da2e5ee37c1a 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -9,6 +9,16 @@ flag {
}
flag {
+ name: "force_foldables_tent_mode_with_screen_wakelock"
+ namespace: "windowing_frontend"
+ description: "Switching displays on a foldable device later if screen wakelock is present"
+ bug: "363174979"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_foldables_posture_based_closed_state"
namespace: "windowing_frontend"
description: "Enables smarter closed device state state for foldable devices"
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
index 2d725d1e2294..c25d5ef09209 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
@@ -45,6 +45,8 @@ import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputSensorInfo;
import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManager.ScreenTimeoutPolicyListener;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.view.Display;
@@ -98,6 +100,8 @@ public final class BookStyleDeviceStatePolicyTest {
@Mock
DisplayManager mDisplayManager;
@Mock
+ PowerManager mPowerManager;
+ @Mock
private Display mDisplay;
private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
@@ -118,6 +122,7 @@ public final class BookStyleDeviceStatePolicyTest {
private Map<Sensor, List<SensorEventListener>> mSensorEventListeners = new HashMap<>();
private DeviceStateProvider mProvider;
+ private BookStyleDeviceStatePolicy mPolicy;
@Before
public void setup() {
@@ -125,6 +130,7 @@ public final class BookStyleDeviceStatePolicyTest {
mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true);
mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true);
+ mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
when(mInputSensorInfo.getName()).thenReturn("hall-effect");
mHallSensor = new Sensor(mInputSensorInfo);
@@ -146,6 +152,7 @@ public final class BookStyleDeviceStatePolicyTest {
when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mDisplay);
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+ mContext.addMockSystemService(PowerManager.class, mPowerManager);
mContext.ensureTestableResources();
when(mContext.getResources().getConfiguration()).thenReturn(mConfiguration);
@@ -592,6 +599,62 @@ public final class BookStyleDeviceStatePolicyTest {
}
@Test
+ public void test_unfoldTo85Degrees_screenWakeLockExists_forceTentModeWithWakeLockEnabled()
+ throws Exception {
+ mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
+ mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+ mPolicy.getDeviceStateProvider().onSystemReady();
+ sendHingeAngle(0f);
+ final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
+ listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ sendHingeAngle(180f);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+ sendHingeAngle(0f);
+ sendHingeAngle(15f);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+ sendHingeAngle(85f);
+
+ // Keeps 'closed' state meaning that it is in 'tent' mode as we have a screen wakelock
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ }
+
+ @Test
+ public void test_unfoldTo85Degrees_noScreenWakelock_forceTentModeWithWakeLockEnabled()
+ throws Exception {
+ mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
+ mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+ mPolicy.getDeviceStateProvider().onSystemReady();
+ sendHingeAngle(0f);
+ final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
+ listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_ACTIVE);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ sendHingeAngle(180f);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+ sendHingeAngle(0f);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+ sendHingeAngle(85f);
+
+ // Switches to half-opened state as we don't have a screen wakelock
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ }
+
+ @Test
+ public void test_unfoldTo85Degrees_notSubscribedToWakeLocks_forceTentModeWithWakeLockDisabled()
+ throws Exception {
+ mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, false);
+ mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+
+ mPolicy.getDeviceStateProvider().onSystemReady();
+
+ verify(mPowerManager, never()).addScreenTimeoutPolicyListener(anyInt(), any(), any());
+ }
+
+ @Test
public void test_foldTo10_leftSideIsFlat_keepsInnerScreenForReverseWedge() {
sendHingeAngle(180f);
sendLeftSideFlatSensorEvent(true);
@@ -751,8 +814,17 @@ public final class BookStyleDeviceStatePolicyTest {
}
private DeviceStateProvider createProvider() {
- return new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
+ mPolicy = new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
mHallSensor, mLeftAccelerometer, mRightAccelerometer,
- /* closeAngleDegrees= */ null).getDeviceStateProvider();
+ /* closeAngleDegrees= */ null);
+ return mPolicy.getDeviceStateProvider();
+ }
+
+ private ScreenTimeoutPolicyListener captureScreenTimeoutPolicyListener() {
+ final ArgumentCaptor<ScreenTimeoutPolicyListener> captor = ArgumentCaptor
+ .forClass(ScreenTimeoutPolicyListener.class);
+ verify(mPowerManager, atLeastOnce())
+ .addScreenTimeoutPolicyListener(anyInt(), any(), captor.capture());
+ return captor.getValue();
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
index 58e4b9177808..8f26bf32335a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -72,8 +72,6 @@ public class BroadcastHelperTest {
private static final String PACKAGE_CHANGED_TEST_PACKAGE_NAME = "testpackagename";
private static final String PACKAGE_CHANGED_TEST_MAIN_ACTIVITY =
PACKAGE_CHANGED_TEST_PACKAGE_NAME + ".MainActivity";
- private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
- "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -125,34 +123,23 @@ public class BroadcastHelperTest {
@RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
@Test
- public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToSystemAndApplicationItself()
throws Exception {
changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
new String[0] /* sharedPackages */);
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
- captor.capture(), eq(null),
- eq(new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}),
- anyInt(), eq(null), eq(null), eq(null));
- Intent intent = captor.getValue();
- assertNotNull(intent);
- assertThat(intent.getPackage()).isEqualTo("android");
- }
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockActivityManagerInternal, times(2)).broadcastIntentWithCallback(
+ captorIntent.capture(), eq(null), eq(null), anyInt(), eq(null), eq(null), eq(null));
+ List<Intent> intents = captorIntent.getAllValues();
+ assertNotNull(intents);
+ assertThat(intents.size()).isEqualTo(2);
- @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
- @Test
- public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
- throws Exception {
- changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
- new String[0] /* sharedPackages */);
+ final Intent intent1 = intents.get(0);
+ assertThat(intent1.getPackage()).isEqualTo("android");
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
- eq(null), anyInt(), eq(null), eq(null), eq(null));
- Intent intent = captor.getValue();
- assertNotNull(intent);
- assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+ final Intent intent2 = intents.get(1);
+ assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
}
@RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
@@ -163,31 +150,20 @@ public class BroadcastHelperTest {
new String[]{"shared.package"} /* sharedPackages */);
ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
- ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass(
- String[].class);
verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback(
- captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(),
- eq(null), eq(null), eq(null));
+ captorIntent.capture(), eq(null), eq(null), anyInt(), eq(null), eq(null), eq(null));
List<Intent> intents = captorIntent.getAllValues();
- List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues();
assertNotNull(intents);
assertThat(intents.size()).isEqualTo(3);
final Intent intent1 = intents.get(0);
- final String[] requiredPermission1 = requiredPermissions.get(0);
assertThat(intent1.getPackage()).isEqualTo("android");
- assertThat(requiredPermission1).isEqualTo(
- new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
final Intent intent2 = intents.get(1);
- final String[] requiredPermission2 = requiredPermissions.get(1);
assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
- assertThat(requiredPermission2).isNull();
final Intent intent3 = intents.get(2);
- final String[] requiredPermission3 = requiredPermissions.get(2);
assertThat(intent3.getPackage()).isEqualTo("shared.package");
- assertThat(requiredPermission3).isNull();
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index f442eb69594e..ebaa2e84c044 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -271,4 +271,31 @@ public class LocationFudgerTest {
assertThat(center[0]).isEqualTo(expected[0]);
assertThat(center[1]).isEqualTo(expected[1]);
}
+
+ @Test
+ public void getS2CellApproximateEdge_returnsCorrectRadius() {
+ int level = 10;
+
+ float radius = mFudger.getS2CellApproximateEdge(level);
+
+ assertThat(radius).isEqualTo(9000); // in meters
+ }
+
+ @Test
+ public void getS2CellApproximateEdge_doesNotThrow() {
+ int level = -1;
+
+ mFudger.getS2CellApproximateEdge(level);
+
+ // No exception thrown.
+ }
+
+ @Test
+ public void getS2CellApproximateEdge_doesNotThrow2() {
+ int level = 14;
+
+ mFudger.getS2CellApproximateEdge(level);
+
+ // No exception thrown.
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 4d8aef49f080..5602fb76e6f5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -1490,7 +1490,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
mFakePermissionEnforcer.revoke(Manifest.permission.STATUS_BAR_SERVICE);
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1503,7 +1502,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
mTestableContext.getTestablePermissions().setPermission(
@@ -1517,7 +1515,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1537,7 +1534,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
List<ComponentName> tiles =
@@ -1554,7 +1550,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1573,7 +1568,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1595,7 +1589,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1618,7 +1611,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
@@ -1636,7 +1628,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
@DisableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE)
public void restoreShortcutTargetsAssumeUser0_qs_a11yQsTargetsRestored() {
assumeTrue("The test is setup to run as a user 0",
@@ -1645,8 +1636,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags({android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT,
- android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE})
+ @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE)
public void restoreShortcutTargets_qs_a11yQsTargetsRestored() {
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
@@ -1669,40 +1659,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @DisableFlags({android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT,
- android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE})
- public void restoreShortcutTargetsAssumeUser0_qs_a11yQsTargetsNotRestored() {
- assumeTrue("The test is setup to run as a user 0",
- mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
- restoreShortcutTargets_qs_a11yQsTargetsNotRestored();
- }
-
- @Test
- @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
- @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE)
- public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() {
- String daltonizerTile =
- AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
- String colorInversionTile =
- AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
- final AccessibilityUserState userState = new AccessibilityUserState(
- mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
- userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
- putShortcutSettingForUser(QUICK_SETTINGS, daltonizerTile, userState.mUserId);
- mA11yms.mUserStates.put(userState.mUserId, userState);
-
- broadcastSettingRestored(
- ShortcutUtils.convertToKey(QUICK_SETTINGS),
- /*newValue=*/colorInversionTile, userState.mUserId);
-
- Set<String> expected = Set.of(daltonizerTile);
- assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS)))
- .containsExactlyElementsIn(expected);
- assertThat(userState.getShortcutTargetsLocked(QUICK_SETTINGS))
- .containsExactlyElementsIn(expected);
- }
-
- @Test
public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() {
setupShortcutTargetServices();
AccessibilityUserState userState = mA11yms.getCurrentUserState();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 9528467f7ad1..39206dcf21ef 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -952,7 +952,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(99, si.getExtras().getInt("x"));
}
- public void testShortcutInfoSaveAndLoad() throws InterruptedException {
+ public void disabled_testShortcutInfoSaveAndLoad() throws InterruptedException {
mRunningUsers.put(USER_11, true);
setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1065,7 +1065,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
dumpUserFile(USER_11);
}
- public void testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException {
+ public void disabled_testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException {
mRunningUsers.put(USER_11, true);
setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1134,7 +1134,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
dumpUserFile(USER_11);
}
- public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
+ public void disabled_testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
mRunningUsers.put(USER_11, true);
setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1211,7 +1211,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(1, si.getRank());
}
- public void testShortcutInfoSaveAndLoad_uri() throws InterruptedException {
+ public void disabled_testShortcutInfoSaveAndLoad_uri() throws InterruptedException {
mRunningUsers.put(USER_11, true);
setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1299,7 +1299,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals("uri_maskable", si.getIconUri());
}
- public void testShortcutInfoSaveAndLoad_forBackup() {
+ public void disabled_testShortcutInfoSaveAndLoad_forBackup() {
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
@@ -1368,7 +1368,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(0, si.getRank());
}
- public void testShortcutInfoSaveAndLoad_forBackup_resId() {
+ public void disabled_testShortcutInfoSaveAndLoad_forBackup_resId() {
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32);
@@ -1438,7 +1438,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(0, si.getRank());
}
- public void testShortcutInfoSaveAndLoad_forBackup_uri() {
+ public void disabled_testShortcutInfoSaveAndLoad_forBackup_uri() {
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon uriIcon = Icon.createWithContentUri("test_uri");
@@ -1547,7 +1547,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
}
- public void testShortcutInfoSaveAndLoad_intents() {
+ public void disabled_testShortcutInfoSaveAndLoad_intents() {
checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_VIEW));
mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.
@@ -1789,7 +1789,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertFalse(mManager.setDynamicShortcuts(list(si2)));
}
- public void testThrottling_localeChanges() {
+ public void disabled_testThrottling_localeChanges() {
prepareCrossProfileDataSet();
dumpsysOnLogcat("Before save & load");
@@ -2078,7 +2078,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
}
- public void testThrottling_resetByInternalCall() throws Exception {
+ public void disabled_testThrottling_resetByInternalCall() throws Exception {
prepareCrossProfileDataSet();
dumpsysOnLogcat("Before save & load");
@@ -2173,7 +2173,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
}
- public void testReportShortcutUsed() {
+ public void disabled_testReportShortcutUsed() {
mRunningUsers.put(USER_11, true);
runWithCaller(CALLING_PACKAGE_1, USER_11, () -> {
@@ -2322,7 +2322,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
getTestContext().getPackageName()));
}
- public void testDumpCheckin() throws IOException {
+ public void disabled_testDumpCheckin() throws IOException {
prepareCrossProfileDataSet();
// prepareCrossProfileDataSet() doesn't set any icons, so do set here.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 4bc286117ac6..704b580a80b0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -116,6 +116,7 @@ import android.content.IContentProvider;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.res.Resources;
@@ -6834,6 +6835,41 @@ public class PreferencesHelperTest extends UiServiceTestCase {
assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
}
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testGetNotificationChannels_createIfNeeded() {
+ // Test setup hasn't created any channels or read package preferences yet.
+ // If we ask for notification channels _without_ creating, we should get no result.
+ ParceledListSlice<NotificationChannel> channels = mHelper.getNotificationChannels(PKG_N_MR1,
+ UID_N_MR1, false, false, /* createPrefsIfNeeded= */ false);
+ assertThat(channels.getList().size()).isEqualTo(0);
+
+ // If we ask it to create package preferences, we expect the default channel to be created
+ // for N_MR1.
+ channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
+ false, /* createPrefsIfNeeded= */ true);
+ assertThat(channels.getList().size()).isEqualTo(1);
+ assertThat(channels.getList().getFirst().getId()).isEqualTo(
+ NotificationChannel.DEFAULT_CHANNEL_ID);
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testGetNotificationChannels_neverCreatesWhenFlagOff() {
+ ParceledListSlice<NotificationChannel> channels;
+ try {
+ channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
+ false, /* createPrefsIfNeeded= */ true);
+ } catch (Exception e) {
+ // Slog.wtf kicks in, presumably
+ } finally {
+ channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
+ false, /* createPrefsIfNeeded= */ false);
+ assertThat(channels.getList().size()).isEqualTo(0);
+ }
+
+ }
+
// Test version of PreferencesHelper whose only functional difference is that it does not
// interact with the real IpcDataCache, and instead tracks whether or not the cache has been
// invalidated since creation or the last reset.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 271364445c6a..018ea58e7120 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -213,7 +213,7 @@ class AppCompatActivityRobot {
void setShouldApplyUserMinAspectRatioOverride(boolean enabled) {
doReturn(enabled).when(mActivityStack.top().mAppCompatController
- .getAppCompatAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
+ .getAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
}
void setShouldCreateCompatDisplayInsets(boolean enabled) {
@@ -226,17 +226,17 @@ class AppCompatActivityRobot {
void setShouldApplyUserFullscreenOverride(boolean enabled) {
doReturn(enabled).when(mActivityStack.top().mAppCompatController
- .getAppCompatAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
+ .getAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
}
void setGetUserMinAspectRatioOverrideCode(@UserMinAspectRatio int overrideCode) {
doReturn(overrideCode).when(mActivityStack.top().mAppCompatController
- .getAppCompatAspectRatioOverrides()).getUserMinAspectRatioOverrideCode();
+ .getAspectRatioOverrides()).getUserMinAspectRatioOverrideCode();
}
void setGetUserMinAspectRatioOverrideValue(float overrideValue) {
doReturn(overrideValue).when(mActivityStack.top().mAppCompatController
- .getAppCompatAspectRatioOverrides()).getUserMinAspectRatio();
+ .getAspectRatioOverrides()).getUserMinAspectRatio();
}
void setIgnoreOrientationRequest(boolean enabled) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index 14ef913e28db..f29cbc6b3360 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -413,7 +413,7 @@ public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
@Override
void onPostActivityCreation(@NonNull ActivityRecord activity) {
super.onPostActivityCreation(activity);
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getAspectRatioOverrides());
}
void checkShouldApplyUserFullscreenOverride(boolean expected) {
@@ -465,7 +465,7 @@ public class AppCompatAspectRatioOverridesTest extends WindowTestsBase {
}
private AppCompatAspectRatioOverrides getTopActivityAppCompatAspectRatioOverrides() {
- return activity().top().mAppCompatController.getAppCompatAspectRatioOverrides();
+ return activity().top().mAppCompatController.getAspectRatioOverrides();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
index d8f845389727..5e49c8cdb917 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
@@ -188,7 +188,7 @@ public class AppCompatFocusOverridesTest extends WindowTestsBase {
}
void checkShouldSendFakeFocusOnTopActivity(boolean expected) {
- Assert.assertEquals(activity().top().mAppCompatController.getAppCompatFocusOverrides()
+ Assert.assertEquals(activity().top().mAppCompatController.getFocusOverrides()
.shouldSendFakeFocus(), expected);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 93fb73edb644..f577c3a51e75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -555,7 +555,7 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase {
@Override
void onPostActivityCreation(@NonNull ActivityRecord activity) {
super.onPostActivityCreation(activity);
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getAspectRatioOverrides());
spyOn(activity.mAppCompatController.getAspectRatioPolicy());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
index f4e1d4967ff5..fa7dcc8ebbc7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
@@ -407,8 +407,8 @@ public class DesktopAppCompatAspectRatioPolicyTests extends WindowTestsBase {
@Override
void onPostActivityCreation(@NonNull ActivityRecord activity) {
super.onPostActivityCreation(activity);
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
- spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+ spyOn(activity.mAppCompatController.getAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
}
void setDesiredAspectRatio(float aspectRatio) {
@@ -417,7 +417,7 @@ public class DesktopAppCompatAspectRatioPolicyTests extends WindowTestsBase {
}
DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() {
- return getTopActivity().mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+ return getTopActivity().mAppCompatController.getDesktopAspectRatioPolicy();
}
float calculateAspectRatio() {
@@ -430,7 +430,7 @@ public class DesktopAppCompatAspectRatioPolicyTests extends WindowTestsBase {
}
float getSplitScreenAspectRatio() {
- return getTopActivity().mAppCompatController.getAppCompatAspectRatioOverrides()
+ return getTopActivity().mAppCompatController.getAspectRatioOverrides()
.getSplitScreenAspectRatio();
}
@@ -442,7 +442,7 @@ public class DesktopAppCompatAspectRatioPolicyTests extends WindowTestsBase {
void checkHasMinAspectRatioOverride(boolean expected) {
assertEquals(expected, this.activity().top().mAppCompatController
- .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(
+ .getDesktopAspectRatioPolicy().hasMinAspectRatioOverride(
this.activity().top().getTask()));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 6d508eabcd52..cdb51fc1c645 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -265,9 +265,9 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
task, /* ignoreOrientationRequest */ true);
- spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
- mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ mActivity.mAppCompatController.getAspectRatioOverrides())
.isUserFullscreenOverrideEnabled();
final int desiredWidth =
@@ -293,9 +293,9 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
task, /* ignoreOrientationRequest */ true);
- spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
- mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ mActivity.mAppCompatController.getAspectRatioOverrides())
.isSystemOverrideToFullscreenEnabled();
final int desiredWidth =
@@ -320,9 +320,9 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
task, /* ignoreOrientationRequest */ true);
- spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+ spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
- .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
+ .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
final int desiredWidth =
(int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f);
@@ -424,7 +424,7 @@ public class DesktopModeLaunchParamsModifierTests extends
(int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
final int desiredWidth =
(int) (desiredHeight / activity.mAppCompatController
- .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio());
+ .getAspectRatioOverrides().getSplitScreenAspectRatio());
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
.setActivity(activity).calculate());
@@ -525,7 +525,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final int desiredWidth =
(int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
final int desiredHeight = (int) (desiredWidth * activity.mAppCompatController
- .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio());
+ .getAspectRatioOverrides().getSplitScreenAspectRatio());
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
.setActivity(activity).calculate());
@@ -616,7 +616,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
task, /* ignoreOrientationRequest */ true);
final float userAspectRatioOverrideValueSplitScreen = activity.mAppCompatController
- .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio();
+ .getAspectRatioOverrides().getSplitScreenAspectRatio();
applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
userAspectRatioOverrideValueSplitScreen);
@@ -641,7 +641,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
task, /* ignoreOrientationRequest */ true);
final float userAspectRatioOverrideValueDisplaySize = activity.mAppCompatController
- .getAppCompatAspectRatioOverrides().getDisplaySizeMinAspectRatio();
+ .getAspectRatioOverrides().getDisplaySizeMinAspectRatio();
applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
userAspectRatioOverrideValueDisplaySize);
@@ -738,7 +738,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
task, /* ignoreOrientationRequest */ true);
final float userAspectRatioOverrideValueSplitScreen = activity.mAppCompatController
- .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio();
+ .getAspectRatioOverrides().getSplitScreenAspectRatio();
applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
userAspectRatioOverrideValueSplitScreen);
@@ -763,7 +763,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
task, /* ignoreOrientationRequest */ true);
final float userAspectRatioOverrideValueDisplaySize = activity.mAppCompatController
- .getAppCompatAspectRatioOverrides().getDisplaySizeMinAspectRatio();
+ .getAspectRatioOverrides().getDisplaySizeMinAspectRatio();
applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
userAspectRatioOverrideValueDisplaySize);
@@ -812,9 +812,9 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
task, /* ignoreOrientationRequest */ true);
- spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+ spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
- .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
+ .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
final int desiredHeight =
(int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
@@ -884,9 +884,9 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
task, /* ignoreOrientationRequest */ true);
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
- activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ activity.mAppCompatController.getAspectRatioOverrides())
.isUserFullscreenOverrideEnabled();
final int desiredWidth =
@@ -912,9 +912,9 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
task, /* ignoreOrientationRequest */ true);
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
- activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ activity.mAppCompatController.getAspectRatioOverrides())
.isSystemOverrideToFullscreenEnabled();
final int desiredWidth =
@@ -939,9 +939,9 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
task, /* ignoreOrientationRequest */ true);
- spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+ spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
- .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
+ .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
@@ -989,9 +989,9 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
task, /* ignoreOrientationRequest */ true);
- spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+ spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
- .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
+ .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
@@ -1294,7 +1294,7 @@ public class DesktopModeLaunchParamsModifierTests extends
private void setDesiredAspectRatio(ActivityRecord activity, float aspectRatio) {
final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
- activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+ activity.mAppCompatController.getDesktopAspectRatioPolicy();
spyOn(desktopAppCompatAspectRatioPolicy);
doReturn(aspectRatio).when(desktopAppCompatAspectRatioPolicy)
.getDesiredAspectRatio(any());
@@ -1304,7 +1304,7 @@ public class DesktopModeLaunchParamsModifierTests extends
float overrideValue) {
// Set desired aspect ratio to be below minimum so override can take effect.
final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
- activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+ activity.mAppCompatController.getDesktopAspectRatioPolicy();
spyOn(desktopAppCompatAspectRatioPolicy);
doReturn(1f).when(desktopAppCompatAspectRatioPolicy)
.getDesiredAspectRatio(any());
@@ -1318,7 +1318,7 @@ public class DesktopModeLaunchParamsModifierTests extends
// Simulate user min aspect ratio override being set.
final AppCompatAspectRatioOverrides appCompatAspectRatioOverrides =
- activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ activity.mAppCompatController.getAspectRatioOverrides();
spyOn(appCompatAspectRatioOverrides);
doReturn(overrideValue).when(appCompatAspectRatioOverrides).getUserMinAspectRatio();
doReturn(overrideCode).when(appCompatAspectRatioOverrides)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index dfd10ec86a20..d76a907ba010 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1583,42 +1583,6 @@ public class DisplayContentTests extends WindowTestsBase {
is(Configuration.ORIENTATION_PORTRAIT));
}
- @Test
- public void testHybridRotationAnimation() {
- final DisplayContent displayContent = mDefaultDisplay;
- final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build();
- final WindowState navBar = newWindowBuilder("navBar", TYPE_NAVIGATION_BAR).build();
- final WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).build();
- final WindowState[] windows = { statusBar, navBar, app };
- makeWindowVisible(windows);
- final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
- displayPolicy.addWindowLw(statusBar, statusBar.mAttrs);
- displayPolicy.addWindowLw(navBar, navBar.mAttrs);
- final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent,
- displayContent.getRotation());
- spyOn(rotationAnim);
- // Assume that the display rotation is changed so it is frozen in preparation for animation.
- doReturn(true).when(rotationAnim).hasScreenshot();
- displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
- displayContent.setRotationAnimation(rotationAnim);
- // The fade rotation animation also starts to hide some non-app windows.
- assertNotNull(displayContent.getAsyncRotationController());
- assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_TOKEN_TRANSFORM));
-
- for (WindowState w : windows) {
- w.setOrientationChanging(true);
- }
- // The display only waits for the app window to unfreeze.
- assertFalse(displayContent.shouldSyncRotationChange(statusBar));
- assertFalse(displayContent.shouldSyncRotationChange(navBar));
- assertTrue(displayContent.shouldSyncRotationChange(app));
- // If all windows animated by fade rotation animation have done the orientation change,
- // the animation controller should be cleared.
- statusBar.setOrientationChanging(false);
- navBar.setOrientationChanging(false);
- assertNull(displayContent.getAsyncRotationController());
- }
-
@SetupWindows(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index b19af0d0809e..a84b374f9487 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1143,11 +1143,11 @@ public class SizeCompatTests extends WindowTestsBase {
// Simulate the user selecting the fullscreen user aspect ratio override
spyOn(activity.mWmService.mAppCompatConfiguration);
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(activity.mWmService.mAppCompatConfiguration)
.isUserAppAspectRatioFullscreenEnabled();
doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
- .when(activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .when(activity.mAppCompatController.getAspectRatioOverrides())
.getUserMinAspectRatioOverrideCode();
assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@@ -1165,9 +1165,9 @@ public class SizeCompatTests extends WindowTestsBase {
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// Simulate the user selecting the fullscreen user aspect ratio override
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
- activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ activity.mAppCompatController.getAspectRatioOverrides())
.isSystemOverrideToFullscreenEnabled();
assertFalse(activity.shouldCreateAppCompatDisplayInsets());
}
@@ -2199,9 +2199,9 @@ public class SizeCompatTests extends WindowTestsBase {
.isUserAppAspectRatioFullscreenEnabled();
// Set user aspect ratio override
- spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
- .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .when(mActivity.mAppCompatController.getAspectRatioOverrides())
.getUserMinAspectRatioOverrideCode();
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
@@ -2224,9 +2224,9 @@ public class SizeCompatTests extends WindowTestsBase {
.isUserAppAspectRatioFullscreenEnabled();
// Set user aspect ratio override
- spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
- .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .when(mActivity.mAppCompatController.getAspectRatioOverrides())
.getUserMinAspectRatioOverrideCode();
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
@@ -2244,9 +2244,9 @@ public class SizeCompatTests extends WindowTestsBase {
final int displayWidth = mDisplayContent.mBaseDisplayWidth;
final int displayHeight = mDisplayContent.mBaseDisplayHeight;
- spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
- mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ mActivity.mAppCompatController.getAspectRatioOverrides())
.isSystemOverrideToFullscreenEnabled();
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
@@ -2264,9 +2264,9 @@ public class SizeCompatTests extends WindowTestsBase {
final int displayWidth = mDisplayContent.mBaseDisplayWidth;
final int displayHeight = mDisplayContent.mBaseDisplayHeight;
- spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
- mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ mActivity.mAppCompatController.getAspectRatioOverrides())
.isSystemOverrideToFullscreenEnabled();
prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
@@ -2416,7 +2416,7 @@ public class SizeCompatTests extends WindowTestsBase {
boolean enabled) {
final ActivityRecord activity = getActivityBuilderWithoutTask().build();
final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
- activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+ activity.mAppCompatController.getDesktopAspectRatioPolicy();
spyOn(desktopAppCompatAspectRatioPolicy);
doReturn(enabled).when(desktopAppCompatAspectRatioPolicy)
.hasMinAspectRatioOverride(any());
@@ -2426,7 +2426,7 @@ public class SizeCompatTests extends WindowTestsBase {
doReturn(enabled).when(activity.mWmService.mAppCompatConfiguration)
.isUserAppAspectRatioSettingsEnabled();
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ activity.mAppCompatController.getAspectRatioOverrides();
spyOn(aspectRatioOverrides);
// Set user aspect ratio override.
doReturn(aspectRatio).when(aspectRatioOverrides).getUserMinAspectRatioOverrideCode();
@@ -3892,11 +3892,12 @@ public class SizeCompatTests extends WindowTestsBase {
mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
// Draw letterbox.
- mActivity.setVisible(false);
- mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
- mActivity.mDisplayContent.mOpeningApps.add(mActivity);
+ mActivity.ensureActivityConfiguration();
addWindowToActivity(mActivity);
mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ // Verify mAppCompatDisplayInsets is created after recomputing.
+ assertTrue(mActivity.providesMaxBounds());
}
private void assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(Rect parentBounds) {
@@ -4357,7 +4358,7 @@ public class SizeCompatTests extends WindowTestsBase {
Configuration parentConfig = mActivity.getParent().getConfiguration();
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ mActivity.mAppCompatController.getAspectRatioOverrides();
float actual = aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(parentConfig);
float expected = aspectRatioOverrides.getSplitScreenAspectRatio();
@@ -4497,7 +4498,7 @@ public class SizeCompatTests extends WindowTestsBase {
.isUserAppAspectRatioSettingsEnabled();
final AppCompatController appCompatController = mActivity.mAppCompatController;
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- appCompatController.getAppCompatAspectRatioOverrides();
+ appCompatController.getAspectRatioOverrides();
spyOn(aspectRatioOverrides);
// Set user aspect ratio override.
doReturn(USER_MIN_ASPECT_RATIO_16_9).when(aspectRatioOverrides)
@@ -4520,7 +4521,7 @@ public class SizeCompatTests extends WindowTestsBase {
.isUserAppAspectRatioSettingsEnabled();
final AppCompatController appCompatController = mActivity.mAppCompatController;
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- appCompatController.getAppCompatAspectRatioOverrides();
+ appCompatController.getAspectRatioOverrides();
spyOn(aspectRatioOverrides);
// Set user aspect ratio override.
doReturn(USER_MIN_ASPECT_RATIO_16_9).when(aspectRatioOverrides)
@@ -5011,7 +5012,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Compat override can still take effect.
final AppCompatAspectRatioOverrides aspectRatioOverrides =
- mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ mActivity.mAppCompatController.getAspectRatioOverrides();
spyOn(aspectRatioOverrides);
doReturn(true).when(aspectRatioOverrides).shouldOverrideMinAspectRatio();
assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index ef58498b351c..546ecc6e38a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -36,6 +36,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TAS
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -1911,6 +1912,53 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
OP_TYPE_REORDER_TO_TOP_OF_TASK);
}
+ @Test
+ public void testApplyTransaction_setCanAffectSystemUiFlags() {
+ mController.unregisterOrganizer(mIOrganizer);
+ registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
+
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf = createTaskFragment(task);
+
+ // Setting the flag to false.
+ TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
+ mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
+
+ assertApplyTransactionAllowed(mTransaction);
+
+ verify(tf).setCanAffectSystemUiFlags(false);
+
+ // Setting the flag back to true.
+ operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(true).build();
+ mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
+
+ assertApplyTransactionAllowed(mTransaction);
+
+ verify(tf).setCanAffectSystemUiFlags(true);
+ }
+
+ @Test
+ public void testApplyTransaction_setCanAffectSystemUiFlags_failsIfNotSystemOrganizer() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf = createTaskFragment(task);
+
+ TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
+ mTransaction
+ .addTaskFragmentOperation(tf.getFragmentToken(), operation)
+ .setErrorCallbackToken(mErrorToken);
+
+ assertApplyTransactionAllowed(mTransaction);
+
+ // The pending event will be dispatched on the handler (from requestTraversal).
+ waitHandlerIdle(mWm.mAnimationHandler);
+
+ assertTaskFragmentErrorTransaction(OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
+ SecurityException.class);
+ }
+
@NonNull
private ActivityRecord setupUntrustedEmbeddingPipReparent() {
final int pid = Binder.getCallingPid();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 1e0cef0514d8..1e16c97de647 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -113,7 +113,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase {
mSnapshotPersistQueue = new SnapshotPersistQueue();
PersistInfoProvider provider =
TaskSnapshotController.createPersistInfoProvider(mWm, userId -> FILES_DIR);
- mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider);
+ mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider, false);
mLoader = new AppSnapshotLoader(provider);
mSnapshotPersistQueue.start();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1febc9fb4742..38d3d2fec942 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -684,9 +684,7 @@ public class TaskTests extends WindowTestsBase {
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
Task task = rootTask.getBottomMostTask();
task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
- DisplayInfo info = new DisplayInfo();
- display.mDisplay.getDisplayInfo(info);
- final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
+ final Rect fullScreenBounds = new Rect(display.getBounds());
final Rect freeformBounds = new Rect(fullScreenBounds);
freeformBounds.inset((int) (freeformBounds.width() * 0.2),
(int) (freeformBounds.height() * 0.2));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index ab9abfc4a876..f6f473b4964d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -855,7 +855,6 @@ public class WindowStateTests extends WindowTestsBase {
startingApp.updateResizingWindowIfNeeded();
assertTrue(mWm.mResizingWindows.contains(startingApp));
assertTrue(startingApp.isDrawn());
- assertFalse(startingApp.getOrientationChanging());
// Even if the display is frozen, invisible requested window should not be affected.
mWm.startFreezingDisplay(0, 0, mDisplayContent);
@@ -873,7 +872,6 @@ public class WindowStateTests extends WindowTestsBase {
win.updateResizingWindowIfNeeded();
assertThat(mWm.mResizingWindows).contains(win);
- assertTrue(win.getOrientationChanging());
mWm.mResizingWindows.remove(win);
spyOn(win.mClient);
@@ -892,7 +890,6 @@ public class WindowStateTests extends WindowTestsBase {
// Even "resized" throws remote exception, it is still considered as reported. So the window
// shouldn't be resized again (which may block unfreeze in real case).
assertThat(mWm.mResizingWindows).doesNotContain(win);
- assertFalse(win.getOrientationChanging());
}
@Test