summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt3
-rw-r--r--core/java/android/app/ActivityManager.java2
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/ApplicationPackageManager.java14
-rw-r--r--core/java/android/app/AutomaticZenRule.aidl4
-rw-r--r--core/java/android/app/AutomaticZenRule.java49
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/app/Instrumentation.java12
-rw-r--r--core/java/android/app/NotificationManager.java10
-rw-r--r--core/java/android/app/PictureInPictureParams.java2
-rw-r--r--core/java/android/app/activity_manager.aconfig7
-rw-r--r--core/java/android/app/admin/StringSetIntersection.java80
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig10
-rw-r--r--core/java/android/app/wallpaper/WallpaperDescription.java6
-rw-r--r--core/java/android/content/pm/PackageManager.java34
-rw-r--r--core/java/android/hardware/display/DisplayTopology.java3
-rw-r--r--core/java/android/os/Parcel.java8
-rw-r--r--core/java/android/os/flags.aconfig9
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java57
-rw-r--r--core/java/android/view/ViewTreeObserver.java6
-rw-r--r--core/java/android/view/flags/view_tree_observer_flags.aconfig9
-rw-r--r--core/java/android/window/WindowContainerTransaction.java19
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressBar.java35
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressDrawable.java68
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/res/drawable/notification_progress.xml5
-rw-r--r--core/res/res/values/attrs.xml24
-rw-r--r--core/res/res/values/styles_material.xml3
-rw-r--r--core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java176
-rw-r--r--core/tests/coretests/src/android/os/ParcelTest.java61
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt93
-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/res/layout/bubble_manage_menu.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt12
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig7
-rw-r--r--libs/hwui/jni/Bitmap.cpp29
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp2
-rw-r--r--packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml28
-rw-r--r--packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml1
-rw-r--r--packages/SettingsLib/StatusBannerPreference/res/values/colors.xml1
-rw-r--r--packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt20
-rw-r--r--packages/Shell/AndroidManifest.xml4
-rw-r--r--packages/SystemUI/TEST_MAPPING20
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig18
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java11
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt41
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt33
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt34
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt19
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt27
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt15
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt15
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json6
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json6
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json50
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json6
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json6
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json44
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt155
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt12
-rw-r--r--packages/SystemUI/res/color/brightness_slider_track.xml19
-rw-r--r--packages/SystemUI/res/drawable/brightness_progress_drawable.xml2
-rw-r--r--packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml21
-rw-r--r--packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml27
-rw-r--r--packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml4
-rw-r--r--packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml6
-rw-r--r--packages/SystemUI/res/values/colors.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml5
-rw-r--r--packages/SystemUI/shared/res/values/bools.xml3
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt223
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt143
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt)69
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt1
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt27
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt18
-rw-r--r--ravenwood/tools/hoststubgen/Android.bp1
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt35
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt96
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt95
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt115
-rw-r--r--services/core/java/com/android/server/adb/AdbDebuggingManager.java44
-rw-r--r--services/core/java/com/android/server/adb/AdbService.java57
-rw-r--r--services/core/java/com/android/server/audio/AudioServerPermissionProvider.java32
-rw-r--r--services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java6
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java4
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java85
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java62
-rw-r--r--services/core/java/com/android/server/location/gnss/hal/GnssNative.java1
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java9
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java9
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java4
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java31
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java30
-rw-r--r--services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java2
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java30
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java5
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java29
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java103
-rw-r--r--services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java38
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java11
-rw-r--r--tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java10
218 files changed, 3063 insertions, 1484 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 4cd6d6f03ee8..216bbab882a9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -34264,6 +34264,7 @@ package android.os {
method public boolean hasFileDescriptors();
method public boolean hasFileDescriptors(int, int);
method public byte[] marshall();
+ method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void marshall(@NonNull java.nio.ByteBuffer);
method @NonNull public static android.os.Parcel obtain();
method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder);
method @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader);
@@ -34333,6 +34334,7 @@ package android.os {
method public void setDataSize(int);
method public void setPropagateAllowBlocking();
method public void unmarshall(@NonNull byte[], int, int);
+ method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void unmarshall(@NonNull java.nio.ByteBuffer);
method public void writeArray(@Nullable Object[]);
method public void writeBinderArray(@Nullable android.os.IBinder[]);
method public void writeBinderList(@Nullable java.util.List<android.os.IBinder>);
@@ -55423,6 +55425,7 @@ package android.view {
method public void dispatchOnDraw();
method public void dispatchOnGlobalLayout();
method public boolean dispatchOnPreDraw();
+ method @FlaggedApi("android.view.flags.enable_dispatch_on_scroll_changed") public void dispatchOnScrollChanged();
method public boolean isAlive();
method public void registerFrameCommitCallback(@NonNull Runnable);
method @Deprecated public void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 62816a2fa0f5..9cc7b8ff47c2 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1188,7 +1188,7 @@ public class ActivityManager {
/** @hide Should this process state be considered jank perceptible? */
public static final boolean isProcStateJankPerceptible(int procState) {
- if (Flags.jankPerceptibleNarrow()) {
+ if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) {
return procState == PROCESS_STATE_PERSISTENT_UI
|| procState == PROCESS_STATE_TOP
|| procState == PROCESS_STATE_IMPORTANT_FOREGROUND
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 96b50960f0a2..f44c2305591d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4014,7 +4014,7 @@ public final class ActivityThread extends ClientTransactionHandler
return VM_PROCESS_STATE_JANK_PERCEPTIBLE;
}
- if (Flags.jankPerceptibleNarrow()) {
+ if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) {
// Unlike other persistent processes, system server is often on
// the critical path for application startup. Mark it explicitly
// as jank perceptible regardless of processState.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 00fa1c1a4f80..bdecbaeb455d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -302,13 +302,23 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public Intent getLaunchIntentForPackage(String packageName) {
+ return getLaunchIntentForPackage(packageName, false);
+ }
+
+ @Override
+ @Nullable
+ public Intent getLaunchIntentForPackage(@NonNull String packageName,
+ boolean includeDirectBootUnaware) {
+ ResolveInfoFlags queryFlags = ResolveInfoFlags.of(
+ includeDirectBootUnaware ? MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE : 0);
+
// First see if the package has an INFO activity; the existence of
// such an activity is implied to be the desired front-door for the
// overall package (such as if it has multiple launcher entries).
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO);
intentToResolve.setPackage(packageName);
- List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0);
+ List<ResolveInfo> ris = queryIntentActivities(intentToResolve, queryFlags);
// Otherwise, try to find a main launcher activity.
if (ris == null || ris.size() <= 0) {
@@ -316,7 +326,7 @@ public class ApplicationPackageManager extends PackageManager {
intentToResolve.removeCategory(Intent.CATEGORY_INFO);
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
intentToResolve.setPackage(packageName);
- ris = queryIntentActivities(intentToResolve, 0);
+ ris = queryIntentActivities(intentToResolve, queryFlags);
}
if (ris == null || ris.size() <= 0) {
return null;
diff --git a/core/java/android/app/AutomaticZenRule.aidl b/core/java/android/app/AutomaticZenRule.aidl
index feb21d657c6a..92f7d5235cf7 100644
--- a/core/java/android/app/AutomaticZenRule.aidl
+++ b/core/java/android/app/AutomaticZenRule.aidl
@@ -16,4 +16,6 @@
package android.app;
-parcelable AutomaticZenRule; \ No newline at end of file
+parcelable AutomaticZenRule;
+
+parcelable AutomaticZenRule.AzrWithId; \ No newline at end of file
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index fa977c93113a..1ce38ace75d8 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -228,7 +228,7 @@ public final class AutomaticZenRule implements Parcelable {
public AutomaticZenRule(Parcel source) {
enabled = source.readInt() == ENABLED;
if (source.readInt() == ENABLED) {
- name = getTrimmedString(source.readString());
+ name = getTrimmedString(source.readString8());
}
interruptionFilter = source.readInt();
conditionId = getTrimmedUri(source.readParcelable(null, android.net.Uri.class));
@@ -238,11 +238,11 @@ public final class AutomaticZenRule implements Parcelable {
source.readParcelable(null, android.content.ComponentName.class));
creationTime = source.readLong();
mZenPolicy = source.readParcelable(null, ZenPolicy.class);
- mPkg = source.readString();
+ mPkg = source.readString8();
mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
mAllowManualInvocation = source.readBoolean();
mIconResId = source.readInt();
- mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
+ mTriggerDescription = getTrimmedString(source.readString8(), MAX_DESC_LENGTH);
mType = source.readInt();
}
@@ -514,7 +514,7 @@ public final class AutomaticZenRule implements Parcelable {
dest.writeInt(enabled ? ENABLED : DISABLED);
if (name != null) {
dest.writeInt(1);
- dest.writeString(name);
+ dest.writeString8(name);
} else {
dest.writeInt(0);
}
@@ -524,11 +524,11 @@ public final class AutomaticZenRule implements Parcelable {
dest.writeParcelable(configurationActivity, 0);
dest.writeLong(creationTime);
dest.writeParcelable(mZenPolicy, 0);
- dest.writeString(mPkg);
+ dest.writeString8(mPkg);
dest.writeParcelable(mDeviceEffects, 0);
dest.writeBoolean(mAllowManualInvocation);
dest.writeInt(mIconResId);
- dest.writeString(mTriggerDescription);
+ dest.writeString8(mTriggerDescription);
dest.writeInt(mType);
}
@@ -843,4 +843,41 @@ public final class AutomaticZenRule implements Parcelable {
return rule;
}
}
+
+ /** @hide */
+ public static final class AzrWithId implements Parcelable {
+ public final String mId;
+ public final AutomaticZenRule mRule;
+
+ public AzrWithId(String id, AutomaticZenRule rule) {
+ mId = id;
+ mRule = rule;
+ }
+
+ public static final Creator<AzrWithId> CREATOR = new Creator<>() {
+ @Override
+ public AzrWithId createFromParcel(Parcel in) {
+ return new AzrWithId(
+ in.readString8(),
+ in.readParcelable(AutomaticZenRule.class.getClassLoader(),
+ AutomaticZenRule.class));
+ }
+
+ @Override
+ public AzrWithId[] newArray(int size) {
+ return new AzrWithId[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mId);
+ dest.writeParcelable(mRule, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ }
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 00df7246a300..1f0cd39a823c 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -224,7 +224,7 @@ interface INotificationManager
void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
ZenPolicy getDefaultZenPolicy();
AutomaticZenRule getAutomaticZenRule(String id);
- Map<String, AutomaticZenRule> getAutomaticZenRules();
+ ParceledListSlice getAutomaticZenRules();
String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg, boolean fromUser);
boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule, boolean fromUser);
boolean removeAutomaticZenRule(String id, boolean fromUser);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 8af5b1bd40f8..19fecb9bf7c2 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -101,14 +101,20 @@ public class Instrumentation {
*/
public static final String REPORT_KEY_STREAMRESULT = "stream";
- static final String TAG = "Instrumentation";
+ /**
+ * @hide
+ */
+ public static final String TAG = "Instrumentation";
private static final long CONNECT_TIMEOUT_MILLIS = 60_000;
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
- // If set, will print the stack trace for activity starts within the process
- static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
+ /**
+ * If set, will print the stack trace for activity starts within the process
+ * @hide
+ */
+ public static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
SystemProperties.getBoolean("persist.wm.debug.start_activity", false);
static final boolean DEBUG_FINISH_ACTIVITY = Build.IS_DEBUGGABLE &&
SystemProperties.getBoolean("persist.wm.debug.finish_activity", false);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 050ef23b89e3..69e3ef9086d5 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1747,7 +1747,15 @@ public class NotificationManager {
public Map<String, AutomaticZenRule> getAutomaticZenRules() {
INotificationManager service = service();
try {
- return service.getAutomaticZenRules();
+ Map<String, AutomaticZenRule> result = new HashMap<>();
+ ParceledListSlice<AutomaticZenRule.AzrWithId> parceledRules =
+ service.getAutomaticZenRules();
+ if (parceledRules != null) {
+ for (AutomaticZenRule.AzrWithId rule : parceledRules.getList()) {
+ result.put(rule.mId, rule.mRule);
+ }
+ }
+ return result;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index afe915eece26..dd87d288566e 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -594,7 +594,7 @@ public final class PictureInPictureParams implements Parcelable {
* @see PictureInPictureParams.Builder#setSeamlessResizeEnabled(boolean)
*/
public boolean isSeamlessResizeEnabled() {
- return mSeamlessResizeEnabled == null ? true : mSeamlessResizeEnabled;
+ return mSeamlessResizeEnabled == null ? false : mSeamlessResizeEnabled;
}
/**
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 720e045dc944..e431426d5d09 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -163,3 +163,10 @@ flag {
description: "Narrow the scope of Jank Perceptible"
bug: "304837972"
}
+
+flag {
+ name: "jank_perceptible_narrow_holdback"
+ namespace: "system_performance"
+ description: "Holdback study for jank_perceptible_narrow"
+ bug: "304837972"
+}
diff --git a/core/java/android/app/admin/StringSetIntersection.java b/core/java/android/app/admin/StringSetIntersection.java
new file mode 100644
index 000000000000..5f2031e93ad9
--- /dev/null
+++ b/core/java/android/app/admin/StringSetIntersection.java
@@ -0,0 +1,80 @@
+/*
+ * 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.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Set;
+
+/**
+ * Class to identify a intersection resolution mechanism for {@code Set<String>} policies, it's
+ * used to resolve the enforced policy when being set by multiple admins (see {@link
+ * PolicyState#getResolutionMechanism()}).
+ *
+ * @hide
+ */
+public final class StringSetIntersection extends ResolutionMechanism<Set<String>> {
+
+ /**
+ * Intersection resolution for policies represented {@code Set<String>} which resolves as the
+ * intersection of all sets.
+ */
+ @NonNull
+ public static final StringSetIntersection STRING_SET_INTERSECTION = new StringSetIntersection();
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ return o != null && getClass() == o.getClass();
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "StringSetIntersection {}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+ @NonNull
+ public static final Parcelable.Creator<StringSetIntersection> CREATOR =
+ new Parcelable.Creator<StringSetIntersection>() {
+ @Override
+ public StringSetIntersection createFromParcel(Parcel source) {
+ return new StringSetIntersection();
+ }
+
+ @Override
+ public StringSetIntersection[] newArray(int size) {
+ return new StringSetIntersection[size];
+ }
+ };
+}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 572bffe6c6a4..b87ef7061c39 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -412,3 +412,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "use_policy_intersection_for_permitted_input_methods"
+ namespace: "enterprise"
+ description: "When deciding on permitted input methods, use policy intersection instead of last recorded policy."
+ bug: "340914586"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index a13af7f1ddcd..d7d6262599da 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -168,6 +168,12 @@ public final class WallpaperDescription implements Parcelable {
return mSampleSize;
}
+ @Override
+ public String toString() {
+ String component = (mComponent != null) ? mComponent.toString() : "{null}";
+ return component + ":" + mId;
+ }
+
////// Comparison overrides
@Override
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 49fd6344270e..53966b8af533 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5964,7 +5964,39 @@ public abstract class PackageManager {
*
* @see #getLaunchIntentSenderForPackage(String)
*/
- public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName);
+ public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName);
+
+ /**
+ * Returns a "good" intent to launch a front-door activity in a package.
+ * This is used, for example, to implement an "open" button when browsing
+ * through packages. The current implementation looks first for a main
+ * activity in the category {@link Intent#CATEGORY_INFO}, and next for a
+ * main activity in the category {@link Intent#CATEGORY_LAUNCHER}. Returns
+ * <code>null</code> if neither are found.
+ *
+ * <p>Consider using {@link #getLaunchIntentSenderForPackage(String)} if
+ * the caller is not allowed to query for the <code>packageName</code>.
+ *
+ * @param packageName The name of the package to inspect.
+ * @param includeDirectBootUnaware When {@code true}, activities that are direct-boot-unaware
+ * will be considered even if the device hasn't been unlocked (i.e. querying will be done
+ * with {@code MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE}).
+ *
+ * @return A fully-qualified {@link Intent} that can be used to launch the
+ * main activity in the package. Returns <code>null</code> if the package
+ * does not contain such an activity, or if <em>packageName</em> is not
+ * recognized.
+ *
+ * @see #getLaunchIntentSenderForPackage(String)
+ *
+ * @hide
+ */
+ public @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName,
+ boolean includeDirectBootUnaware) {
+ throw new UnsupportedOperationException(
+ "getLaunchIntentForPackage(packageName, includeDirectBootUnaware) not implemented"
+ + " in subclass");
+ }
/**
* Return a "good" intent to launch a front-door Leanback activity in a
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index 4ed0fc056e7d..c3c8c3d63bec 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -600,8 +600,7 @@ public final class DisplayTopology implements Parcelable {
private void addDisplay(int displayId, float width, float height, boolean shouldLog) {
if (findDisplay(displayId, mRoot) != null) {
- throw new IllegalArgumentException(
- "DisplayTopology: attempting to add a display that already exists");
+ return;
}
if (mRoot == null) {
mRoot = new TreeNode(displayId, width, height, POSITION_LEFT, /* offset= */ 0);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 6cb49b3ea166..4a9928532b93 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,6 +28,7 @@ import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.AppOpsManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Flags;
import android.ravenwood.annotation.RavenwoodClassLoadHook;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodReplace;
@@ -837,9 +839,8 @@ public final class Parcel {
* @param buffer The ByteBuffer to write the data to.
* @throws ReadOnlyBufferException if the buffer is read-only.
* @throws BufferOverflowException if the buffer is too small.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER)
public final void marshall(@NonNull ByteBuffer buffer) {
if (buffer == null) {
throw new NullPointerException();
@@ -875,9 +876,8 @@ public final class Parcel {
* Fills the raw bytes of this Parcel with data from the supplied buffer.
*
* @param buffer will read buffer.remaining() bytes from the buffer.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER)
public final void unmarshall(@NonNull ByteBuffer buffer) {
if (buffer == null) {
throw new NullPointerException();
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 86acb2b21cfa..0150d171d51c 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -354,6 +354,15 @@ flag {
flag {
namespace: "system_performance"
+ name: "parcel_marshall_bytebuffer"
+ is_exported: true
+ description: "Parcel marshal/unmarshall APIs that use ByteBuffer."
+ is_fixed_read_only: true
+ bug: "401362825"
+}
+
+flag {
+ namespace: "system_performance"
name: "perfetto_sdk_tracing"
description: "Tracing using Perfetto SDK."
bug: "303199244"
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 4cbd5beb3a8c..fce2df185461 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -62,6 +62,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
@@ -460,14 +461,21 @@ public class ZenModeConfig implements Parcelable {
}
private static void readRulesFromParcel(ArrayMap<String, ZenRule> ruleMap, Parcel source) {
- final int len = source.readInt();
+ int len = source.readInt();
if (len > 0) {
final String[] ids = new String[len];
- final ZenRule[] rules = new ZenRule[len];
- source.readStringArray(ids);
- source.readTypedArray(rules, ZenRule.CREATOR);
+ source.readString8Array(ids);
+ ParceledListSlice<?> parceledRules = source.readParcelable(
+ ZenRule.class.getClassLoader(), ParceledListSlice.class);
+ List<?> rules = parceledRules != null ? parceledRules.getList() : new ArrayList<>();
+ if (rules.size() != len) {
+ Slog.wtf(TAG, String.format(
+ "Unexpected parceled rules count (%s != %s), throwing them out",
+ rules.size(), len));
+ len = 0;
+ }
for (int i = 0; i < len; i++) {
- ruleMap.put(ids[i], rules[i]);
+ ruleMap.put(ids[i], (ZenRule) rules.get(i));
}
}
}
@@ -485,8 +493,8 @@ public class ZenModeConfig implements Parcelable {
}
dest.writeInt(user);
dest.writeParcelable(manualRule, 0);
- writeRulesToParcel(automaticRules, dest);
- writeRulesToParcel(deletedRules, dest);
+ writeRulesToParcel(automaticRules, dest, flags);
+ writeRulesToParcel(deletedRules, dest, flags);
if (!Flags.modesUi()) {
dest.writeInt(allowAlarms ? 1 : 0);
dest.writeInt(allowMedia ? 1 : 0);
@@ -501,18 +509,19 @@ public class ZenModeConfig implements Parcelable {
}
}
- private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest) {
+ private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest,
+ int flags) {
if (!ruleMap.isEmpty()) {
final int len = ruleMap.size();
final String[] ids = new String[len];
- final ZenRule[] rules = new ZenRule[len];
+ final ArrayList<ZenRule> rules = new ArrayList<>();
for (int i = 0; i < len; i++) {
ids[i] = ruleMap.keyAt(i);
- rules[i] = ruleMap.valueAt(i);
+ rules.add(ruleMap.valueAt(i));
}
dest.writeInt(len);
- dest.writeStringArray(ids);
- dest.writeTypedArray(rules, 0);
+ dest.writeString8Array(ids);
+ dest.writeParcelable(new ParceledListSlice<>(rules), flags);
} else {
dest.writeInt(0);
}
@@ -2636,7 +2645,7 @@ public class ZenModeConfig implements Parcelable {
enabled = source.readInt() == 1;
snoozing = source.readInt() == 1;
if (source.readInt() == 1) {
- name = source.readString();
+ name = source.readString8();
}
zenMode = source.readInt();
conditionId = source.readParcelable(null, android.net.Uri.class);
@@ -2644,18 +2653,18 @@ public class ZenModeConfig implements Parcelable {
component = source.readParcelable(null, android.content.ComponentName.class);
configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
if (source.readInt() == 1) {
- id = source.readString();
+ id = source.readString8();
}
creationTime = source.readLong();
if (source.readInt() == 1) {
- enabler = source.readString();
+ enabler = source.readString8();
}
zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
- pkg = source.readString();
+ pkg = source.readString8();
allowManualInvocation = source.readBoolean();
- iconResName = source.readString();
- triggerDescription = source.readString();
+ iconResName = source.readString8();
+ triggerDescription = source.readString8();
type = source.readInt();
userModifiedFields = source.readInt();
zenPolicyUserModifiedFields = source.readInt();
@@ -2703,7 +2712,7 @@ public class ZenModeConfig implements Parcelable {
dest.writeInt(snoozing ? 1 : 0);
if (name != null) {
dest.writeInt(1);
- dest.writeString(name);
+ dest.writeString8(name);
} else {
dest.writeInt(0);
}
@@ -2714,23 +2723,23 @@ public class ZenModeConfig implements Parcelable {
dest.writeParcelable(configurationActivity, 0);
if (id != null) {
dest.writeInt(1);
- dest.writeString(id);
+ dest.writeString8(id);
} else {
dest.writeInt(0);
}
dest.writeLong(creationTime);
if (enabler != null) {
dest.writeInt(1);
- dest.writeString(enabler);
+ dest.writeString8(enabler);
} else {
dest.writeInt(0);
}
dest.writeParcelable(zenPolicy, 0);
dest.writeParcelable(zenDeviceEffects, 0);
- dest.writeString(pkg);
+ dest.writeString8(pkg);
dest.writeBoolean(allowManualInvocation);
- dest.writeString(iconResName);
- dest.writeString(triggerDescription);
+ dest.writeString8(iconResName);
+ dest.writeString8(triggerDescription);
dest.writeInt(type);
dest.writeInt(userModifiedFields);
dest.writeInt(zenPolicyUserModifiedFields);
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 3b444c44c368..fc66e49ff6b0 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -16,6 +16,9 @@
package android.view;
+import static android.view.flags.Flags.FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1258,8 +1261,9 @@ public final class ViewTreeObserver {
/**
* Notifies registered listeners that something has scrolled.
*/
+ @FlaggedApi(FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED)
@UnsupportedAppUsage
- final void dispatchOnScrollChanged() {
+ public final void dispatchOnScrollChanged() {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
diff --git a/core/java/android/view/flags/view_tree_observer_flags.aconfig b/core/java/android/view/flags/view_tree_observer_flags.aconfig
new file mode 100644
index 000000000000..82f3300a87cb
--- /dev/null
+++ b/core/java/android/view/flags/view_tree_observer_flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.view.flags"
+container: "system"
+
+flag {
+ name: "enable_dispatch_on_scroll_changed"
+ namespace: "toolkit"
+ description: "Feature flag for enabling the dispatchOnScrollChanged method in ViewTreeObserver."
+ bug: "238109286"
+} \ No newline at end of file
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2ed9c3a48add..8f7f94196eb1 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -16,6 +16,7 @@
package android.window;
+import static android.app.Instrumentation.DEBUG_START_ACTIVITY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -32,6 +33,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
+import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -45,6 +47,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
+import android.util.Log;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.SurfaceControl;
@@ -642,6 +645,10 @@ public final class WindowContainerTransaction implements Parcelable {
*/
@NonNull
public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
+ if (DEBUG_START_ACTIVITY) {
+ Log.d(Instrumentation.TAG, "WCT.startTask: taskId=" + taskId
+ + " options=" + options, new Throwable());
+ }
mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
return this;
}
@@ -655,11 +662,15 @@ public final class WindowContainerTransaction implements Parcelable {
*/
@NonNull
public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender,
- @Nullable Intent intent, @Nullable Bundle options) {
+ @Nullable Intent fillInIntent, @Nullable Bundle options) {
+ if (DEBUG_START_ACTIVITY) {
+ Log.d(Instrumentation.TAG, "WCT.sendPendingIntent: sender=" + sender.getIntent()
+ + " fillInIntent=" + fillInIntent + " options=" + options, new Throwable());
+ }
mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT)
.setLaunchOptions(options)
.setPendingIntent(sender)
- .setActivityIntent(intent)
+ .setActivityIntent(fillInIntent)
.build());
return this;
}
@@ -674,6 +685,10 @@ public final class WindowContainerTransaction implements Parcelable {
@NonNull
public WindowContainerTransaction startShortcut(@NonNull String callingPackage,
@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
+ if (DEBUG_START_ACTIVITY) {
+ Log.d(Instrumentation.TAG, "WCT.startShortcut: shortcutInfo=" + shortcutInfo
+ + " options=" + options, new Throwable());
+ }
mHierarchyOps.add(HierarchyOp.createForStartShortcut(
callingPackage, shortcutInfo, options));
return this;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 816270235446..8dd0457248a4 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -287,6 +287,17 @@ flag {
}
flag {
+ name: "use_visible_requested_for_process_tracker"
+ namespace: "windowing_frontend"
+ description: "Do not count closing activity as visible process"
+ bug: "396653764"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "ensure_wallpaper_in_transitions"
namespace: "windowing_frontend"
description: "Ensure that wallpaper window tokens are always present/available for collection in transitions"
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 3472d681a486..f6e2a4df8cca 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -78,6 +78,13 @@ public final class NotificationProgressBar extends ProgressBar implements
@Nullable
private List<DrawablePart> mProgressDrawableParts = null;
+ /** @see R.styleable#NotificationProgressBar_segMinWidth */
+ private final float mSegMinWidth;
+ /** @see R.styleable#NotificationProgressBar_segSegGap */
+ private final float mSegSegGap;
+ /** @see R.styleable#NotificationProgressBar_segPointGap */
+ private final float mSegPointGap;
+
@Nullable
private Drawable mTracker = null;
private boolean mHasTrackerIcon = false;
@@ -128,6 +135,10 @@ public final class NotificationProgressBar extends ProgressBar implements
Log.e(TAG, "Can't get NotificationProgressDrawable", ex);
}
+ mSegMinWidth = a.getDimension(R.styleable.NotificationProgressBar_segMinWidth, 0f);
+ mSegSegGap = a.getDimension(R.styleable.NotificationProgressBar_segSegGap, 0f);
+ mSegPointGap = a.getDimension(R.styleable.NotificationProgressBar_segPointGap, 0f);
+
// Supports setting the tracker in xml, but ProgressStyle notifications set/override it
// via {@code #setProgressTrackerIcon}.
final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker);
@@ -444,30 +455,26 @@ public final class NotificationProgressBar extends ProgressBar implements
return;
}
- final float segSegGap = mNotificationProgressDrawable.getSegSegGap();
- final float segPointGap = mNotificationProgressDrawable.getSegPointGap();
final float pointRadius = mNotificationProgressDrawable.getPointRadius();
mProgressDrawableParts = processPartsAndConvertToDrawableParts(
mParts,
width,
- segSegGap,
- segPointGap,
+ mSegSegGap,
+ mSegPointGap,
pointRadius,
mHasTrackerIcon,
mTrackerDrawWidth
);
- final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
final float progressFraction = getProgressFraction();
final boolean isStyledByProgress = mProgressModel.isStyledByProgress();
- final float progressGap =
- mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap();
+ final float progressGap = mHasTrackerIcon ? 0F : mSegSegGap;
Pair<List<DrawablePart>, Float> p = null;
try {
p = maybeStretchAndRescaleSegments(
mParts,
mProgressDrawableParts,
- segmentMinWidth,
+ mSegMinWidth,
pointRadius,
progressFraction,
isStyledByProgress,
@@ -492,11 +499,11 @@ public final class NotificationProgressBar extends ProgressBar implements
mProgressModel.getProgress(),
getMax(),
width,
- segSegGap,
- segPointGap,
+ mSegSegGap,
+ mSegPointGap,
pointRadius,
mHasTrackerIcon,
- segmentMinWidth,
+ mSegMinWidth,
isStyledByProgress,
mTrackerDrawWidth);
} catch (NotEnoughWidthToFitAllPartsException ex) {
@@ -521,11 +528,11 @@ public final class NotificationProgressBar extends ProgressBar implements
mProgressModel.getProgress(),
getMax(),
width,
- segSegGap,
- segPointGap,
+ mSegSegGap,
+ mSegPointGap,
pointRadius,
mHasTrackerIcon,
- segmentMinWidth,
+ mSegMinWidth,
isStyledByProgress,
mTrackerDrawWidth);
} catch (NotEnoughWidthToFitAllPartsException ex) {
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index b1096107f04b..32b283af29c5 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -84,27 +84,6 @@ public final class NotificationProgressDrawable extends Drawable {
}
/**
- * Returns the gap between two segments.
- */
- public float getSegSegGap() {
- return mState.mSegSegGap;
- }
-
- /**
- * Returns the gap between a segment and a point.
- */
- public float getSegPointGap() {
- return mState.mSegPointGap;
- }
-
- /**
- * Returns the gap between a segment and a point.
- */
- public float getSegmentMinWidth() {
- return mState.mSegmentMinWidth;
- }
-
- /**
* Returns the radius for the points.
*/
public float getPointRadius() {
@@ -241,11 +220,6 @@ public final class NotificationProgressDrawable extends Drawable {
mState.setDensity(resolveDensity(r, 0));
- final TypedArray a = obtainAttributes(r, theme, attrs,
- R.styleable.NotificationProgressDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
-
inflateChildElements(r, parser, attrs, theme);
updateLocalState();
@@ -262,13 +236,6 @@ public final class NotificationProgressDrawable extends Drawable {
state.setDensity(resolveDensity(t.getResources(), 0));
- if (state.mThemeAttrs != null) {
- final TypedArray a = t.resolveAttributes(
- state.mThemeAttrs, R.styleable.NotificationProgressDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
- }
-
applyThemeChildElements(t);
updateLocalState();
@@ -279,21 +246,6 @@ public final class NotificationProgressDrawable extends Drawable {
return (mState.canApplyTheme()) || super.canApplyTheme();
}
- private void updateStateFromTypedArray(TypedArray a) {
- final State state = mState;
-
- // Account for any configuration changes.
- state.mChangingConfigurations |= a.getChangingConfigurations();
-
- // Extract the theme attributes, if any.
- state.mThemeAttrs = a.extractThemeAttrs();
-
- state.mSegSegGap = a.getDimension(R.styleable.NotificationProgressDrawable_segSegGap,
- state.mSegSegGap);
- state.mSegPointGap = a.getDimension(R.styleable.NotificationProgressDrawable_segPointGap,
- state.mSegPointGap);
- }
-
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
TypedArray a;
@@ -357,8 +309,6 @@ public final class NotificationProgressDrawable extends Drawable {
// Extract the theme attributes, if any.
state.mThemeAttrsSegments = a.extractThemeAttrs();
- state.mSegmentMinWidth = a.getDimension(
- R.styleable.NotificationProgressDrawableSegments_minWidth, state.mSegmentMinWidth);
state.mSegmentHeight = a.getDimension(
R.styleable.NotificationProgressDrawableSegments_height, state.mSegmentHeight);
state.mFadedSegmentHeight = a.getDimension(
@@ -588,9 +538,6 @@ public final class NotificationProgressDrawable extends Drawable {
static final class State extends ConstantState {
@Config
int mChangingConfigurations;
- float mSegSegGap = 0.0f;
- float mSegPointGap = 0.0f;
- float mSegmentMinWidth = 0.0f;
float mSegmentHeight;
float mFadedSegmentHeight;
float mSegmentCornerRadius;
@@ -610,9 +557,6 @@ public final class NotificationProgressDrawable extends Drawable {
State(@NonNull State orig, @Nullable Resources res) {
mChangingConfigurations = orig.mChangingConfigurations;
- mSegSegGap = orig.mSegSegGap;
- mSegPointGap = orig.mSegPointGap;
- mSegmentMinWidth = orig.mSegmentMinWidth;
mSegmentHeight = orig.mSegmentHeight;
mFadedSegmentHeight = orig.mFadedSegmentHeight;
mSegmentCornerRadius = orig.mSegmentCornerRadius;
@@ -631,18 +575,6 @@ public final class NotificationProgressDrawable extends Drawable {
}
private void applyDensityScaling(int sourceDensity, int targetDensity) {
- if (mSegSegGap > 0) {
- mSegSegGap = scaleFromDensity(
- mSegSegGap, sourceDensity, targetDensity);
- }
- if (mSegPointGap > 0) {
- mSegPointGap = scaleFromDensity(
- mSegPointGap, sourceDensity, targetDensity);
- }
- if (mSegmentMinWidth > 0) {
- mSegmentMinWidth = scaleFromDensity(
- mSegmentMinWidth, sourceDensity, targetDensity);
- }
if (mSegmentHeight > 0) {
mSegmentHeight = scaleFromDensity(
mSegmentHeight, sourceDensity, targetDensity);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e16ce9849ff2..9e0200481421 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5394,13 +5394,13 @@
corresponding permission such as {@link #HEAD_TRACKING} or
{@link #FACE_TRACKING} for the data being accessed.
- <p>Protection level: normal|appop
+ <p>Protection level: signature|privileged
@SystemApi
@FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
@hide -->
<permission android:name="android.permission.XR_TRACKING_IN_BACKGROUND"
- android:protectionLevel="normal|appop"
+ android:protectionLevel="signature|privileged"
android:description="@string/permdesc_xr_tracking_in_background"
android:label="@string/permlab_xr_tracking_in_background"
android:featureFlag="android.xr.xr_manifest_entries" />
diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml
index ff5450ee106f..92a0a6a4e21b 100644
--- a/core/res/res/drawable/notification_progress.xml
+++ b/core/res/res/drawable/notification_progress.xml
@@ -19,12 +19,9 @@
android:gravity="center_vertical|fill_horizontal">
<com.android.internal.widget.NotificationProgressDrawable
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:segSegGap="@dimen/notification_progress_segSeg_gap"
- android:segPointGap="@dimen/notification_progress_segPoint_gap">
+ android:layout_height="wrap_content">
<segments
android:color="?attr/colorProgressBackgroundNormal"
- android:minWidth="@dimen/notification_progress_segments_min_width"
android:height="@dimen/notification_progress_segments_height"
android:fadedHeight="@dimen/notification_progress_segments_faded_height"
android:cornerRadius="@dimen/notification_progress_segments_corner_radius"/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d2c993aecb0d..647e3dc2d268 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5573,6 +5573,14 @@
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressBar">
+ <!-- Minimum required drawing width for segments. The drawing width refers to the width
+ after the original segments have been adjusted for the neighboring Points and gaps.
+ This is enforced by stretching the segments that are too short. -->
+ <attr name="segMinWidth" format="dimension" />
+ <!-- The gap between two segments. -->
+ <attr name="segSegGap" format="dimension" />
+ <!-- The gap between a segment and a point. -->
+ <attr name="segPointGap" format="dimension" />
<!-- Draws the tracker on a NotificationProgressBar. -->
<attr name="tracker" format="reference" />
<!-- Height of the tracker. -->
@@ -7580,25 +7588,9 @@
<!-- NotificationProgressDrawable class -->
<!-- ================================== -->
- <!-- Drawable used to render a notification progress bar, with segments and points. -->
- <!-- @hide internal use only -->
- <declare-styleable name="NotificationProgressDrawable">
- <!-- The gap between two segments. -->
- <attr name="segSegGap" format="dimension" />
- <!-- The gap between a segment and a point. -->
- <attr name="segPointGap" format="dimension" />
- </declare-styleable>
-
<!-- Used to config the segments of a NotificationProgressDrawable. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawableSegments">
- <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only
- place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
- above. -->
- <!-- Minimum required drawing width. The drawing width refers to the width after
- the original segments have been adjusted for the neighboring Points and gaps. This is
- enforced by stretching the segments that are too short. -->
- <attr name="minWidth" />
<!-- Height of the solid segments. -->
<attr name="height" />
<!-- Height of the faded segments. -->
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 73681d26f297..8f13ee1ccb49 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -503,6 +503,9 @@ please see styles_device_defaults.xml.
<style name="Widget.Material.Notification.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" />
<style name="Widget.Material.Notification.NotificationProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal">
+ <item name="segMinWidth">@dimen/notification_progress_segments_min_width</item>
+ <item name="segSegGap">@dimen/notification_progress_segSeg_gap</item>
+ <item name="segPointGap">@dimen/notification_progress_segPoint_gap</item>
<item name="progressDrawable">@drawable/notification_progress</item>
<item name="trackerHeight">@dimen/notification_progress_tracker_height</item>
</style>
diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
index 62d89f6dc846..146b386e5a4b 100644
--- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
+++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
@@ -16,19 +16,37 @@
package android.app;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_INFO;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.os.storage.VolumeInfo.STATE_MOUNTED;
import static android.os.storage.VolumeInfo.STATE_UNMOUNTED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -44,6 +62,7 @@ import com.android.internal.annotations.VisibleForTesting;
import junit.framework.TestCase;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.xmlpull.v1.XmlPullParser;
@@ -102,14 +121,14 @@ public class ApplicationPackageManagerTest extends TestCase {
sVolumes.add(sPrivateUnmountedVol);
}
- private static final class MockedApplicationPackageManager extends ApplicationPackageManager {
+ public static class MockedApplicationPackageManager extends ApplicationPackageManager {
private boolean mForceAllowOnExternal = false;
private boolean mAllow3rdPartyOnInternal = true;
private HashMap<ApplicationInfo, Resources> mResourcesMap;
public MockedApplicationPackageManager() {
super(null, null);
- mResourcesMap = new HashMap<ApplicationInfo, Resources>();
+ mResourcesMap = new HashMap<>();
}
public void setForceAllowOnExternal(boolean forceAllowOnExternal) {
@@ -153,7 +172,7 @@ public class ApplicationPackageManagerTest extends TestCase {
}
private StorageManager getMockedStorageManager() {
- StorageManager storageManager = Mockito.mock(StorageManager.class);
+ StorageManager storageManager = mock(StorageManager.class);
Mockito.when(storageManager.getVolumes()).thenReturn(sVolumes);
Mockito.when(storageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL))
.thenReturn(sInternalVol);
@@ -190,7 +209,7 @@ public class ApplicationPackageManagerTest extends TestCase {
sysAppInfo.flags = ApplicationInfo.FLAG_SYSTEM;
StorageManager storageManager = getMockedStorageManager();
- IPackageManager pm = Mockito.mock(IPackageManager.class);
+ IPackageManager pm = mock(IPackageManager.class);
MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
@@ -220,7 +239,7 @@ public class ApplicationPackageManagerTest extends TestCase {
ApplicationInfo appInfo = new ApplicationInfo();
StorageManager storageManager = getMockedStorageManager();
- IPackageManager pm = Mockito.mock(IPackageManager.class);
+ IPackageManager pm = mock(IPackageManager.class);
Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(false);
MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
@@ -249,7 +268,7 @@ public class ApplicationPackageManagerTest extends TestCase {
ApplicationInfo appInfo = new ApplicationInfo();
StorageManager storageManager = getMockedStorageManager();
- IPackageManager pm = Mockito.mock(IPackageManager.class);
+ IPackageManager pm = mock(IPackageManager.class);
MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
appPkgMgr.setForceAllowOnExternal(true);
@@ -291,15 +310,15 @@ public class ApplicationPackageManagerTest extends TestCase {
public void testExtractPackageItemInfoAttributes_noMetaData() {
final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
- final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
+ final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null,
new int[]{})).isNull();
}
public void testExtractPackageItemInfoAttributes_noParser() {
final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
- final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
- final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
+ final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
+ final ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo);
assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null,
new int[]{})).isNull();
@@ -307,8 +326,8 @@ public class ApplicationPackageManagerTest extends TestCase {
public void testExtractPackageItemInfoAttributes_noMetaDataXml() {
final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
- final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
- final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
+ final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
+ final ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo);
when(packageItemInfo.loadXmlMetaData(any(), any())).thenReturn(null);
assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null,
@@ -318,9 +337,9 @@ public class ApplicationPackageManagerTest extends TestCase {
public void testExtractPackageItemInfoAttributes_nonMatchingRootTag() throws Exception {
final String rootTag = "rootTag";
final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
- final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
- final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
- final XmlResourceParser parser = Mockito.mock(XmlResourceParser.class);
+ final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
+ final ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+ final XmlResourceParser parser = mock(XmlResourceParser.class);
when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo);
packageItemInfo.metaData = new Bundle();
@@ -334,11 +353,11 @@ public class ApplicationPackageManagerTest extends TestCase {
public void testExtractPackageItemInfoAttributes_successfulExtraction() throws Exception {
final String rootTag = "rootTag";
final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
- final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
- final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
- final XmlResourceParser parser = Mockito.mock(XmlResourceParser.class);
- final Resources resources = Mockito.mock(Resources.class);
- final TypedArray attributes = Mockito.mock(TypedArray.class);
+ final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
+ final ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+ final XmlResourceParser parser = mock(XmlResourceParser.class);
+ final Resources resources = mock(Resources.class);
+ final TypedArray attributes = mock(TypedArray.class);
when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo);
packageItemInfo.metaData = new Bundle();
@@ -351,4 +370,123 @@ public class ApplicationPackageManagerTest extends TestCase {
assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, rootTag,
new int[]{})).isEqualTo(attributes);
}
+
+ public void testGetLaunchIntentForPackage_categoryInfoActivity_returnsIt() throws Exception {
+ String pkg = "com.some.package";
+ int userId = 42;
+ ResolveInfo categoryInfoResolveInfo = new ResolveInfo();
+ categoryInfoResolveInfo.activityInfo = new ActivityInfo();
+ categoryInfoResolveInfo.activityInfo.packageName = pkg;
+ categoryInfoResolveInfo.activityInfo.name = "activity";
+ Intent baseIntent = new Intent(ACTION_MAIN).setPackage(pkg);
+
+ final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager());
+ doReturn(userId).when(pm).getUserId();
+ doReturn(List.of(categoryInfoResolveInfo))
+ .when(pm).queryIntentActivitiesAsUser(
+ eqIntent(new Intent(baseIntent).addCategory(CATEGORY_INFO)),
+ any(ResolveInfoFlags.class),
+ anyInt());
+ doReturn(
+ List.of())
+ .when(pm).queryIntentActivitiesAsUser(
+ eqIntent(new Intent(baseIntent).addCategory(CATEGORY_LAUNCHER)),
+ any(ResolveInfoFlags.class),
+ anyInt());
+
+ Intent intent = pm.getLaunchIntentForPackage(pkg, true);
+
+ assertThat(intent).isNotNull();
+ assertThat(intent.getComponent()).isEqualTo(new ComponentName(pkg, "activity"));
+ assertThat(intent.getCategories()).containsExactly(CATEGORY_INFO);
+ assertThat(intent.getFlags()).isEqualTo(FLAG_ACTIVITY_NEW_TASK);
+ verify(pm).queryIntentActivitiesAsUser(
+ eqIntent(new Intent(ACTION_MAIN).addCategory(CATEGORY_INFO).setPackage(pkg)),
+ eqResolveInfoFlags(MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE),
+ eq(userId));
+ }
+
+ public void testGetLaunchIntentForPackage_categoryLauncherActivity_returnsIt() {
+ String pkg = "com.some.package";
+ int userId = 42;
+ ResolveInfo categoryLauncherResolveInfo1 = new ResolveInfo();
+ categoryLauncherResolveInfo1.activityInfo = new ActivityInfo();
+ categoryLauncherResolveInfo1.activityInfo.packageName = pkg;
+ categoryLauncherResolveInfo1.activityInfo.name = "activity1";
+ ResolveInfo categoryLauncherResolveInfo2 = new ResolveInfo();
+ categoryLauncherResolveInfo2.activityInfo = new ActivityInfo();
+ categoryLauncherResolveInfo2.activityInfo.packageName = pkg;
+ categoryLauncherResolveInfo2.activityInfo.name = "activity2";
+ Intent baseIntent = new Intent(ACTION_MAIN).setPackage(pkg);
+
+ final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager());
+ doReturn(userId).when(pm).getUserId();
+ doReturn(List.of())
+ .when(pm).queryIntentActivitiesAsUser(
+ eqIntent(new Intent(baseIntent).addCategory(CATEGORY_INFO)),
+ any(ResolveInfoFlags.class),
+ anyInt());
+ doReturn(
+ List.of(categoryLauncherResolveInfo1, categoryLauncherResolveInfo2))
+ .when(pm).queryIntentActivitiesAsUser(
+ eqIntent(new Intent(baseIntent).addCategory(CATEGORY_LAUNCHER)),
+ any(ResolveInfoFlags.class),
+ anyInt());
+
+ Intent intent = pm.getLaunchIntentForPackage(pkg, true);
+
+ assertThat(intent).isNotNull();
+ assertThat(intent.getComponent()).isEqualTo(new ComponentName(pkg, "activity1"));
+ assertThat(intent.getCategories()).containsExactly(CATEGORY_LAUNCHER);
+ assertThat(intent.getFlags()).isEqualTo(FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ public void testGetLaunchIntentForPackage_noSuitableActivity_returnsNull() throws Exception {
+ String pkg = "com.some.package";
+ int userId = 42;
+
+ final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager());
+ doReturn(userId).when(pm).getUserId();
+ doReturn(List.of())
+ .when(pm).queryIntentActivitiesAsUser(
+ any(),
+ any(ResolveInfoFlags.class),
+ anyInt());
+
+ Intent intent = pm.getLaunchIntentForPackage(pkg, true);
+
+ assertThat(intent).isNull();
+ }
+
+ /** Equality check for intents -- ignoring extras */
+ private static Intent eqIntent(Intent wanted) {
+ return argThat(
+ new ArgumentMatcher<>() {
+ @Override
+ public boolean matches(Intent argument) {
+ return wanted.filterEquals(argument)
+ && wanted.getFlags() == argument.getFlags();
+ }
+
+ @Override
+ public String toString() {
+ return wanted.toString();
+ }
+ });
+ }
+
+ private static ResolveInfoFlags eqResolveInfoFlags(long flagsWanted) {
+ return argThat(
+ new ArgumentMatcher<>() {
+ @Override
+ public boolean matches(ResolveInfoFlags argument) {
+ return argument.getValue() == flagsWanted;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(flagsWanted);
+ }
+ });
+ }
}
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index bb059108d4b6..3e6520106ab0 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -29,8 +29,6 @@ import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -418,63 +416,4 @@ public class ParcelTest {
int binderEndPos = pA.dataPosition();
assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos));
}
-
- private static final byte[] TEST_DATA = new byte[] {4, 8, 15, 16, 23, 42};
-
- // Allow for some Parcel overhead
- private static final int TEST_DATA_LENGTH = TEST_DATA.length + 100;
-
- @Test
- public void testMarshall_ByteBuffer_wrapped() {
- ByteBuffer bb = ByteBuffer.allocate(TEST_DATA_LENGTH);
- testMarshall_ByteBuffer(bb);
- }
-
- @Test
- public void testMarshall_DirectByteBuffer() {
- ByteBuffer bb = ByteBuffer.allocateDirect(TEST_DATA_LENGTH);
- testMarshall_ByteBuffer(bb);
- }
-
- private void testMarshall_ByteBuffer(ByteBuffer bb) {
- // Ensure that Parcel respects the starting offset by not starting at 0
- bb.position(1);
- bb.mark();
-
- // Parcel test data, then marshall into the ByteBuffer
- Parcel p1 = Parcel.obtain();
- p1.writeByteArray(TEST_DATA);
- p1.marshall(bb);
- p1.recycle();
-
- assertTrue(bb.position() > 1);
- bb.reset();
-
- // Unmarshall test data into a new Parcel
- Parcel p2 = Parcel.obtain();
- bb.reset();
- p2.unmarshall(bb);
- assertTrue(bb.position() > 1);
- p2.setDataPosition(0);
- byte[] marshalled = p2.marshall();
-
- bb.reset();
- for (int i = 0; i < TEST_DATA.length; i++) {
- assertEquals(bb.get(), marshalled[i]);
- }
-
- byte[] testDataCopy = new byte[TEST_DATA.length];
- p2.setDataPosition(0);
- p2.readByteArray(testDataCopy);
- for (int i = 0; i < TEST_DATA.length; i++) {
- assertEquals(TEST_DATA[i], testDataCopy[i]);
- }
-
- // Test that overflowing the buffer throws an exception
- bb.reset();
- // Leave certainly not enough room for the test data
- bb.limit(bb.position() + TEST_DATA.length - 1);
- assertThrows(BufferOverflowException.class, () -> p2.marshall(bb));
- p2.recycle();
- }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 14c15210252a..90011f4018f6 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -24,6 +24,9 @@ import android.graphics.PointF
import android.graphics.Rect
import android.os.Handler
import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.view.IWindowManager
import android.view.MotionEvent
import android.view.View
@@ -36,6 +39,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
+import com.android.wm.shell.Flags
import com.android.wm.shell.R
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.bubbles.Bubble
@@ -64,6 +68,10 @@ import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.bubbles.DeviceConfig
+import com.android.wm.shell.shared.bubbles.DragZone
+import com.android.wm.shell.shared.bubbles.DragZoneFactory
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
+import com.android.wm.shell.shared.bubbles.DraggedObject
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -88,6 +96,8 @@ class BubbleBarLayerViewTest {
const val SCREEN_HEIGHT = 1000
}
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
@get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -101,6 +111,7 @@ class BubbleBarLayerViewTest {
private lateinit var bgExecutor: TestShellExecutor
private lateinit var bubbleLogger: BubbleLogger
private lateinit var testBubblesList: MutableList<Bubble>
+ private lateinit var dragZoneFactory: DragZoneFactory
@Before
fun setUp() {
@@ -134,6 +145,10 @@ class BubbleBarLayerViewTest {
whenever(bubbleData.bubbles).thenReturn(testBubblesList)
whenever(bubbleData.hasBubbles()).thenReturn(!testBubblesList.isEmpty())
+ dragZoneFactory = DragZoneFactory(context, deviceConfig,
+ { SplitScreenMode.UNSUPPORTED },
+ { false })
+
bubbleController =
createBubbleController(
bubbleData,
@@ -280,6 +295,7 @@ class BubbleBarLayerViewTest {
assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
+ @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_BUBBLE_ANYTHING)
@Test
fun testEventLogging_dragExpandedViewLeft() {
val bubble = createBubble("first")
@@ -287,7 +303,7 @@ class BubbleBarLayerViewTest {
getInstrumentation().runOnMainSync {
bubbleBarLayerView.showExpandedView(bubble)
- bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
+ bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
}
waitForExpandedViewAnimation()
@@ -305,6 +321,7 @@ class BubbleBarLayerViewTest {
assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
+ @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_BUBBLE_ANYTHING)
@Test
fun testEventLogging_dragExpandedViewRight() {
val bubble = createBubble("first")
@@ -312,7 +329,7 @@ class BubbleBarLayerViewTest {
getInstrumentation().runOnMainSync {
bubbleBarLayerView.showExpandedView(bubble)
- bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
+ bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
}
waitForExpandedViewAnimation()
@@ -330,6 +347,76 @@ class BubbleBarLayerViewTest {
assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
+ @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE)
+ @Test
+ fun testEventLogging_dragExpandedViewLeft_bubbleAnything() {
+ val bubble = createBubble("first")
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.showExpandedView(bubble)
+ bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
+ }
+ waitForExpandedViewAnimation()
+
+ val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+ assertThat(handleView).isNotNull()
+
+ val dragZones = dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.RIGHT))
+ val rightDragZone = dragZones.filterIsInstance<DragZone.Bubble.Right>().first()
+ val rightPoint = PointF(rightDragZone.bounds.centerX().toFloat(),
+ rightDragZone.bounds.centerY().toFloat())
+ val leftDragZone = dragZones.filterIsInstance<DragZone.Bubble.Left>().first()
+ val leftPoint = PointF(leftDragZone.bounds.centerX().toFloat(),
+ leftDragZone.bounds.centerY().toFloat())
+
+ // Drag from right to left
+ handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightPoint)
+ handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftPoint)
+ handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftPoint)
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE)
+ @Test
+ fun testEventLogging_dragExpandedViewRight_bubbleAnything() {
+ val bubble = createBubble("first")
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.showExpandedView(bubble)
+ bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
+ }
+ waitForExpandedViewAnimation()
+
+ val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+ assertThat(handleView).isNotNull()
+
+ val dragZones = dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+ val rightDragZone = dragZones.filterIsInstance<DragZone.Bubble.Right>().first()
+ val rightPoint = PointF(rightDragZone.bounds.centerX().toFloat(),
+ rightDragZone.bounds.centerY().toFloat())
+ val leftDragZone = dragZones.filterIsInstance<DragZone.Bubble.Left>().first()
+ val leftPoint = PointF(leftDragZone.bounds.centerX().toFloat(),
+ leftDragZone.bounds.centerY().toFloat())
+
+ // Drag from left to right
+ handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftPoint)
+ handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightPoint)
+ handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightPoint)
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
@Test
fun testUpdateExpandedView_updateLocation() {
bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
@@ -385,7 +472,7 @@ class BubbleBarLayerViewTest {
bubbleLogger,
)
// Mark visible so we don't wait for task view before animations can start
- bubbleBarExpandedView.onContentVisibilityChanged(true)
+ bubbleBarExpandedView.onContentVisibilityChanged(true /* visible */)
val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView)
return FakeBubbleFactory.createChatBubble(context, key, viewInfo).also {
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 9bb51a87c08f..ef30d8965452 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -34,6 +34,7 @@
android:src="@drawable/bubble_ic_settings"/>
<TextView
+ android:id="@+id/education_manage_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
@@ -45,6 +46,7 @@
android:text="@string/bubble_bar_education_manage_title"/>
<TextView
+ android:id="@+id/education_manage_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
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 1616707954f5..9076d6a87678 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -34,6 +34,7 @@
android:src="@drawable/ic_floating_landscape"/>
<TextView
+ android:id="@+id/education_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
@@ -45,6 +46,7 @@
android:text="@string/bubble_bar_education_stack_title"/>
<TextView
+ android:id="@+id/education_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 225303b2d942..17ebac95e1dd 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -40,6 +40,7 @@
android:tint="@color/bubbles_icon_tint"/>
<TextView
+ android:id="@+id/manage_dismiss"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@@ -67,6 +68,7 @@
android:tint="@color/bubbles_icon_tint"/>
<TextView
+ android:id="@+id/manage_dont_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 426c3ee5b853..290ef1633819 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -27,6 +27,7 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+import static com.android.wm.shell.shared.TypefaceUtils.setTypeface;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -71,6 +72,7 @@ import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.shared.TriangleShape;
+import com.android.wm.shell.shared.TypefaceUtils;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.taskview.TaskView;
@@ -551,6 +553,7 @@ public class BubbleExpandedView extends LinearLayout {
mManageButton = (AlphaOptimizedButton) LayoutInflater.from(ctw).inflate(
R.layout.bubble_manage_button, this /* parent */, false /* attach */);
addView(mManageButton);
+ setTypeface(mManageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
mManageButton.setVisibility(visibility);
setManageClickListener();
post(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index da6948d947d8..92007a4df71e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -50,6 +50,7 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.R;
import com.android.wm.shell.shared.TriangleShape;
+import com.android.wm.shell.shared.TypefaceUtils;
/**
* Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually
@@ -165,8 +166,10 @@ public class BubbleFlyoutView extends FrameLayout {
LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);
mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container);
mSenderText = findViewById(R.id.bubble_flyout_name);
+ TypefaceUtils.setTypeface(mSenderText, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
mSenderAvatar = findViewById(R.id.bubble_flyout_avatar);
mMessageText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);
+ TypefaceUtils.setTypeface(mMessageText, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM);
final Resources res = getResources();
mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 64f54b8ab5be..e901e0c07fe4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -46,6 +46,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ContrastColorUtil;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
+import com.android.wm.shell.shared.TypefaceUtils;
import java.util.ArrayList;
import java.util.List;
@@ -234,6 +235,10 @@ public class BubbleOverflowContainerView extends LinearLayout {
setBackgroundColor(bgColor);
mEmptyStateTitle.setTextColor(textColor);
mEmptyStateSubtitle.setTextColor(textColor);
+ TypefaceUtils.setTypeface(mEmptyStateTitle,
+ TypefaceUtils.FontFamily.GSF_BODY_MEDIUM_EMPHASIZED);
+ TypefaceUtils.setTypeface(mEmptyStateSubtitle, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM);
+
}
public void updateFontSize() {
@@ -322,6 +327,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
viewName.setTextColor(textColor);
+ TypefaceUtils.setTypeface(viewName, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
return new ViewHolder(overflowView, mPositioner);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index dd5a23aae7f9..3dce45690cf2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -89,6 +89,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
import com.android.wm.shell.bubbles.animation.StackAnimationController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
@@ -1397,6 +1399,14 @@ public class BubbleStackView extends FrameLayout
// The menu itself should respect locale direction so the icons are on the correct side.
mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
addView(mManageMenu);
+
+ // Doesn't seem to work unless view is added; so set font after.
+ TypefaceUtils.setTypeface(findViewById(R.id.manage_dismiss), FontFamily.GSF_LABEL_LARGE);
+ TypefaceUtils.setTypeface(findViewById(R.id.manage_dont_bubble),
+ FontFamily.GSF_LABEL_LARGE);
+ TypefaceUtils.setTypeface(mManageSettingsText, FontFamily.GSF_LABEL_LARGE);
+ TypefaceUtils.setTypeface(findViewById(R.id.bubble_manage_menu_fullscreen_title),
+ FontFamily.GSF_LABEL_LARGE);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 39a2a7b868a0..d2ad70886fa1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -27,6 +27,7 @@ import android.widget.Button
import android.widget.LinearLayout
import com.android.internal.R.color.system_neutral1_900
import com.android.wm.shell.R
+import com.android.wm.shell.shared.TypefaceUtils
import com.android.wm.shell.shared.animation.Interpolators
/**
@@ -53,6 +54,12 @@ class ManageEducationView(
init {
LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
+ TypefaceUtils.setTypeface(findViewById(R.id.user_education_title),
+ TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+ TypefaceUtils.setTypeface(findViewById(R.id.user_education_description),
+ TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
+ TypefaceUtils.setTypeface(manageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED)
+ TypefaceUtils.setTypeface(gotItButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED)
visibility = View.GONE
elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 16606198b240..9ac059890dd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -26,6 +26,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.util.ContrastColorUtil
import com.android.wm.shell.R
+import com.android.wm.shell.shared.TypefaceUtils
import com.android.wm.shell.shared.animation.Interpolators
/**
@@ -59,6 +60,9 @@ class StackEducationView(
init {
LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this)
+ TypefaceUtils.setTypeface(titleTextView,
+ TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+ TypefaceUtils.setTypeface(descTextView, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
visibility = View.GONE
elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 9d4f904e55d0..35435569d8b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -203,7 +203,11 @@ class BubbleBarExpandedViewDragController(
draggedObject: MagnetizedObject<*>,
) {
dragListener.onReleased(inDismiss = true)
- pinController.onDragEnd()
+ if (dropTargetManager != null) {
+ dropTargetManager.onDragEnded()
+ } else {
+ pinController.onDragEnd()
+ }
dismissView.hide()
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 3997412ab459..2cc9387bd1e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -147,15 +147,23 @@ public class BubbleBarLayerView extends FrameLayout
Log.w(TAG, "dropped invalid bubble: " + mExpandedBubble);
return;
}
+
+ final boolean isBubbleLeft = zone instanceof DragZone.Bubble.Left;
+ final boolean isBubbleRight = zone instanceof DragZone.Bubble.Right;
+ if (!isBubbleLeft && !isBubbleRight) {
+ // If we didn't finish the "change" animation make sure to animate
+ // it back to the right spot
+ locationChangeListener.onChange(mInitialLocation);
+ }
if (zone instanceof DragZone.FullScreen) {
((Bubble) mExpandedBubble).getTaskView().moveToFullscreen();
// Make sure location change listener is updated with the initial
// location -- even if we "switched sides" during the drag, since
// we've ended up in fullscreen, the location shouldn't change.
locationChangeListener.onRelease(mInitialLocation);
- } else if (zone instanceof DragZone.Bubble.Left) {
+ } else if (isBubbleLeft) {
locationChangeListener.onRelease(BubbleBarLocation.LEFT);
- } else if (zone instanceof DragZone.Bubble.Right) {
+ } else if (isBubbleRight) {
locationChangeListener.onRelease(BubbleBarLocation.RIGHT);
}
}
@@ -189,7 +197,7 @@ public class BubbleBarLayerView extends FrameLayout
@NonNull
@Override
public SplitScreenMode getSplitScreenMode() {
- return SplitScreenMode.NONE;
+ return SplitScreenMode.UNSUPPORTED;
}
},
new DragZoneFactory.DesktopWindowModeChecker() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
index 6c14d83dfafa..bccc6dcd91db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -25,6 +25,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.wm.shell.R;
+import com.android.wm.shell.shared.TypefaceUtils;
/**
* Bubble bar expanded view menu item view to display menu action details
@@ -55,6 +56,7 @@ public class BubbleBarMenuItemView extends LinearLayout {
super.onFinishInflate();
mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
mTextView = findViewById(R.id.bubble_bar_menu_item_title);
+ TypefaceUtils.setTypeface(mTextView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index dfbf655bb6fc..7c0f8e138aae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -33,6 +33,7 @@ import androidx.core.widget.ImageViewCompat;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
+import com.android.wm.shell.shared.TypefaceUtils;
import java.util.ArrayList;
@@ -75,6 +76,7 @@ public class BubbleBarMenuView extends LinearLayout {
mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
+ TypefaceUtils.setTypeface(mBubbleTitleView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM);
mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon);
updateThemeColors();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 7adec39c9d66..0bd3a54ceeaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -35,6 +35,7 @@ import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
import com.android.wm.shell.bubbles.BubbleEducationController
import com.android.wm.shell.bubbles.BubbleViewProvider
import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.shared.TypefaceUtils
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.bubbles.BubblePopupDrawable
import com.android.wm.shell.shared.bubbles.BubblePopupView
@@ -108,6 +109,10 @@ class BubbleEducationViewController(private val context: Context, private val li
root.getBoundsOnScreen(rootBounds)
educationView =
createEducationView(R.layout.bubble_bar_stack_education, root).apply {
+ TypefaceUtils.setTypeface(findViewById(R.id.education_title),
+ TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+ TypefaceUtils.setTypeface(findViewById(R.id.education_text),
+ TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
updateEducationPosition(view = this, position, rootBounds)
val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
@@ -153,6 +158,10 @@ class BubbleEducationViewController(private val context: Context, private val li
educationView =
createEducationView(R.layout.bubble_bar_manage_education, root).apply {
+ TypefaceUtils.setTypeface(findViewById(R.id.education_manage_title),
+ TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+ TypefaceUtils.setTypeface(findViewById(R.id.education_manage_text),
+ TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
pivotY = 0f
doOnLayout { it.pivotX = it.width / 2f }
setOnClickListener { hideEducation(animated = true) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index dd5827af97d9..320de2ae5945 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -142,8 +142,8 @@ transaction which is applied.
## Tracing activity starts & finishes in the app process
It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
-(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
-get this trace:
+or via a `WindowContainerTransaction` (ie. if you are repro'ing a bug related to activity starts).
+You can enable this system property to get this trace:
```shell
# Enabling
adb shell setprop persist.wm.debug.start_activity true
@@ -168,6 +168,21 @@ adb shell setprop persist.wm.debug.finish_activity \"\"
adb reboot
```
+## Tracing transition requests in the Shell
+
+To trace where a new WM transition is started in the Shell, you can enable this system property:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.start_shell_transition true
+adb reboot
+adb logcat -s "ShellTransitions"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_shell_transition \"\"
+adb reboot
+```
+
+
## Dumps
Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
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 cef18f55b86d..c58bb6e3bd31 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
@@ -40,7 +40,6 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
@@ -341,23 +340,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
return false;
}
- /**
- * @return a change representing a config-at-end activity for a given parent.
- */
- @Nullable
- public TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info,
- @android.annotation.NonNull WindowContainerToken parent) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getTaskInfo() == null
- && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)
- && change.getParent() != null && change.getParent().equals(parent)) {
- return change;
- }
- }
- return null;
- }
-
-
/** Whether a particular package is same as current pip package. */
public boolean isPackageActiveInPip(@Nullable String packageName) {
// No-op, to be handled differently in PIP1 and PIP2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index 880e143a0e13..92f36d08a941 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -43,6 +43,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.util.ArrayList;
import java.util.List;
+import java.util.StringJoiner;
/**
* A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via
@@ -114,6 +115,17 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
// Set the new params but make sure mPictureInPictureParams is not null.
mPictureInPictureParams = params == null
? new PictureInPictureParams.Builder().build() : params;
+ logRemoteActions(mPictureInPictureParams);
+ }
+
+ private void logRemoteActions(@android.annotation.NonNull PictureInPictureParams params) {
+ StringJoiner sj = new StringJoiner("|", "[", "]");
+ if (params.hasSetActions()) {
+ params.getActions().forEach((action) -> sj.add(action.getTitle()));
+ }
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "PIP remote actions=%s", sj.toString());
}
/** Add a PipParamsChangedCallback listener. */
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 cfcd56393bc2..5d8d8b685a23 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
@@ -76,6 +76,7 @@ import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
import com.android.wm.shell.pip2.phone.transition.PipExpandHandler;
+import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
@@ -387,8 +388,8 @@ public class PipTransition extends PipTransitionController implements
mFinishCallback = finishCallback;
// We expect the PiP activity as a separate change in a config-at-end transition;
// only flings are not using config-at-end for resize bounds changes
- TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info,
- pipChange.getTaskInfo().getToken());
+ TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange(
+ info, pipChange.getTaskInfo().getToken());
if (pipActivityChange != null) {
// Transform calculations use PiP params by default, so make sure they are null to
// default to using bounds for scaling calculations instead.
@@ -427,8 +428,8 @@ public class PipTransition extends PipTransitionController implements
}
// We expect the PiP activity as a separate change in a config-at-end transition.
- TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info,
- pipChange.getTaskInfo().getToken());
+ TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange(
+ info, pipChange.getTaskInfo().getToken());
if (pipActivityChange == null) {
return false;
}
@@ -497,8 +498,8 @@ public class PipTransition extends PipTransitionController implements
}
// We expect the PiP activity as a separate change in a config-at-end transition.
- TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info,
- pipChange.getTaskInfo().getToken());
+ TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange(
+ info, pipChange.getTaskInfo().getToken());
if (pipActivityChange == null) {
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java
index 01cda6c91108..e562f3340217 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java
@@ -67,6 +67,36 @@ public class PipTransitionUtils {
}
/**
+ * @return a change representing a config-at-end activity for ancestor.
+ */
+ @Nullable
+ public static TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info,
+ @NonNull WindowContainerToken ancestor) {
+ final TransitionInfo.Change ancestorChange =
+ PipTransitionUtils.getChangeByToken(info, ancestor);
+ if (ancestorChange == null) return null;
+
+ // Iterate through changes bottom-to-top, going up the parent chain starting with ancestor.
+ TransitionInfo.Change lastPipChildChange = ancestorChange;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == ancestorChange) continue;
+
+ if (change.getParent() != null
+ && change.getParent().equals(lastPipChildChange.getContainer())) {
+ // Found a child of the last cached child along the ancestral chain.
+ lastPipChildChange = change;
+ if (change.getTaskInfo() == null
+ && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)) {
+ // If this is a config-at-end activity change, then we found the chain leaf.
+ return change;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* @return the leash to interact with the container this change represents.
* @throws NullPointerException if the leash is null.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 1853ffa96dfc..320a63a95302 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -34,6 +34,7 @@ import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
@@ -132,7 +133,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
TransitionInfo.Change pipActivityChange = null;
if (pipChange != null) {
- pipActivityChange = mPipHandler.getDeferConfigActivityChange(
+ pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange(
info, pipChange.getContainer());
everythingElse.getChanges().remove(pipActivityChange);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index e28a7fa159c5..003ef1d453fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -39,6 +39,7 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
@@ -52,6 +53,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -138,6 +140,10 @@ public class Transitions implements RemoteCallable<Transitions>,
ShellCommandHandler.ShellCommandActionHandler {
static final String TAG = "ShellTransitions";
+ // If set, will print the stack trace for transition starts within the process
+ static final boolean DEBUG_START_TRANSITION = Build.IS_DEBUGGABLE &&
+ SystemProperties.getBoolean("persist.wm.debug.start_shell_transition", false);
+
/** Set to {@code true} to enable shell transitions. */
public static final boolean ENABLE_SHELL_TRANSITIONS = getShellTransitEnabled();
public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
@@ -346,10 +352,10 @@ public class Transitions implements RemoteCallable<Transitions>,
mShellController = shellController;
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Default");
// Next lowest priority is remote transitions.
mHandlers.add(mRemoteTransitionHandler);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
mHomeTransitionObserver = homeTransitionObserver;
mFocusTransitionObserver = focusTransitionObserver;
@@ -439,7 +445,7 @@ public class Transitions implements RemoteCallable<Transitions>,
mHandlers.add(handler);
// Set initial scale settings.
handler.setAnimScaleSetting(mTransitionAnimationScaleSetting);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: %s",
handler.getClass().getSimpleName());
}
@@ -691,7 +697,7 @@ public class Transitions implements RemoteCallable<Transitions>,
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
info.getDebugId(), transitionToken, info.toString(" " /* prefix */));
int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
@@ -753,7 +759,7 @@ public class Transitions implements RemoteCallable<Transitions>,
if (tr.isIdle()) continue;
hadPreceding = true;
// Sleep starts a process of forcing all prior transitions to finish immediately
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ ProtoLog.v(WM_SHELL_TRANSITIONS,
"Start finish-for-sync track %d", i);
finishForSync(active.mToken, i, null /* forceFinish */);
}
@@ -797,7 +803,7 @@ public class Transitions implements RemoteCallable<Transitions>,
if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) {
// No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "No transition roots in %s so"
+ " abort", active);
onAbort(active);
return true;
@@ -839,7 +845,7 @@ public class Transitions implements RemoteCallable<Transitions>,
&& allOccluded)) {
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ ProtoLog.v(WM_SHELL_TRANSITIONS,
"Non-visible anim so abort: %s", active);
onAbort(active);
return true;
@@ -873,7 +879,7 @@ public class Transitions implements RemoteCallable<Transitions>,
void processReadyQueue(Track track) {
if (track.mReadyTransitions.isEmpty()) {
if (track.mActiveTransition == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "Track %d became idle",
mTracks.indexOf(track));
if (areTracksIdle()) {
if (!mReadyDuringSync.isEmpty()) {
@@ -885,7 +891,7 @@ public class Transitions implements RemoteCallable<Transitions>,
if (!success) break;
}
} else if (mPendingTransitions.isEmpty()) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "All active transition "
+ "animations finished");
mKnownTransitions.clear();
// Run all runnables from the run-when-idle queue.
@@ -926,7 +932,7 @@ public class Transitions implements RemoteCallable<Transitions>,
onMerged(playingToken, readyToken);
return;
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " %s is still animating. Notify the animating transition"
+ " in case they can be merged", ready, playing);
mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
@@ -955,7 +961,7 @@ public class Transitions implements RemoteCallable<Transitions>,
}
final Track track = mTracks.get(playing.getTrack());
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
merged, playing);
int readyIdx = 0;
if (track.mReadyTransitions.isEmpty() || track.mReadyTransitions.get(0) != merged) {
@@ -996,7 +1002,7 @@ public class Transitions implements RemoteCallable<Transitions>,
}
private void playTransition(@NonNull ActiveTransition active) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
final var token = active.mToken;
for (int i = 0; i < mObservers.size(); ++i) {
@@ -1007,12 +1013,12 @@ public class Transitions implements RemoteCallable<Transitions>,
// If a handler already chose to run this animation, try delegating to it first.
if (active.mHandler != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
boolean consumed = active.mHandler.startAnimation(token, active.mInfo,
active.mStartT, active.mFinishT, (wct) -> onFinish(token, wct));
if (consumed) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by firstHandler");
mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.instant(TRACE_TAG_WINDOW_MANAGER,
@@ -1042,14 +1048,14 @@ public class Transitions implements RemoteCallable<Transitions>,
) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
if (mHandlers.get(i) == skip) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " skip handler %s",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " skip handler %s",
mHandlers.get(i));
continue;
}
boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT,
finishCB);
if (consumed) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
@@ -1155,7 +1161,7 @@ public class Transitions implements RemoteCallable<Transitions>,
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition animation finished "
+ "(aborted=%b), notifying core %s", active.mAborted, active);
if (active.mStartT != null) {
// Applied by now, so clear immediately to remove any references. Do not set to null
@@ -1209,7 +1215,7 @@ public class Transitions implements RemoteCallable<Transitions>,
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
request.getDebugId(), transitionToken, request);
if (mKnownTransitions.containsKey(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
@@ -1228,6 +1234,8 @@ public class Transitions implements RemoteCallable<Transitions>,
if (requestResult != null) {
active.mHandler = requestResult.first;
wct = requestResult.second;
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition (#%d): request handled by %s",
+ request.getDebugId(), active.mHandler.getClass().getSimpleName());
}
if (request.getDisplayChange() != null) {
TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
@@ -1273,8 +1281,12 @@ public class Transitions implements RemoteCallable<Transitions>,
*/
public IBinder startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "Directly starting a new transition "
+ "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler);
+ if (DEBUG_START_TRANSITION) {
+ Log.d(TAG, "startTransition: type=" + transitTypeToString(type)
+ + " wct=" + wct + " handler=" + handler.getClass().getName(), new Throwable());
+ }
final ActiveTransition active =
new ActiveTransition(mOrganizer.startNewTransition(type, wct));
active.mHandler = handler;
@@ -1362,7 +1374,7 @@ public class Transitions implements RemoteCallable<Transitions>,
}
// Attempt to merge a SLEEP info to signal that the playing transition needs to
// fast-forward.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
+ " into %s via a SLEEP proxy", nextSync, playing);
playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, dummyT,
playing.mToken, (wct) -> {});
@@ -1598,7 +1610,7 @@ public class Transitions implements RemoteCallable<Transitions>,
public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
throws RemoteException {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)",
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)",
t.getId());
mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
iBinder, transitionInfo, t, finishT));
@@ -1784,8 +1796,9 @@ public class Transitions implements RemoteCallable<Transitions>,
pw.println(prefix + TAG);
final String innerPrefix = prefix + " ";
- pw.println(prefix + "Handlers:");
- for (TransitionHandler handler : mHandlers) {
+ pw.println(prefix + "Handlers (ordered by priority):");
+ for (int i = mHandlers.size() - 1; i >= 0; i--) {
+ final TransitionHandler handler = mHandlers.get(i);
pw.print(innerPrefix);
pw.print(handler.getClass().getSimpleName());
pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bcf9396ff0c1..989550c60c0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -1756,8 +1756,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
disposeStatusBarInputLayer();
- mWindowDecorViewHolder.close();
- mWindowDecorViewHolder = null;
+ if (mWindowDecorViewHolder != null) {
+ mWindowDecorViewHolder.close();
+ mWindowDecorViewHolder = null;
+ }
if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
notifyNoCaptionHandle();
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index 509f4f202b6b..8e1cf167318e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -254,6 +254,16 @@ fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
}
}
+/**
+ * Checks that surfaces are still within the expected region after snapping to a snap point.
+ *
+ * @param component The component we are checking (should be one of the two split apps)
+ * @param landscapePosLeft If [true], and device is in left/right split, app is on the left side of
+ * the screen. Has no meaning if device is in top/bottom split.
+ * @param portraitPosTop If [true], and device is in top/bottom split, app is on the top side of
+ * the screen. Has no meaning if device is in left/right split.
+ * @param rotation The rotation state of the display.
+ */
fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
component: IComponentMatcher,
landscapePosLeft: Boolean,
@@ -268,10 +278,12 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
visibleRegion(component).isNotEmpty()
visibleRegion(component)
.coversAtMost(
+ // TODO (b/403082705): Should use the new method for determining left/right split.
if (displayBounds.width() > displayBounds.height()) {
if (landscapePosLeft) {
Region(
- 0,
+ // TODO (b/403304310): Check if we're in an offscreen-enabled mode.
+ -displayBounds.right, // the receding app can go offscreen
0,
(dividerRegion.left + dividerRegion.right) / 2,
displayBounds.bottom
@@ -280,7 +292,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
Region(
(dividerRegion.left + dividerRegion.right) / 2,
0,
- displayBounds.right,
+ displayBounds.right * 2, // the receding app can go offscreen
displayBounds.bottom
)
}
@@ -288,7 +300,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
if (portraitPosTop) {
Region(
0,
- 0,
+ -displayBounds.bottom, // the receding app can go offscreen
displayBounds.right,
(dividerRegion.top + dividerRegion.bottom) / 2
)
@@ -297,7 +309,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
0,
(dividerRegion.top + dividerRegion.bottom) / 2,
displayBounds.right,
- displayBounds.bottom
+ displayBounds.bottom * 2 // the receding app can go offscreen
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 49d6877a1654..e4183f16ba14 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -310,12 +310,18 @@ object SplitScreenUtils {
}
}
+ /**
+ * Drags the divider, then releases, making it snap to a new snap point.
+ */
fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+ // Find the first display that is turned on (making the assumption that there is only one).
val displayBounds =
- wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
- ?: error("Display not found")
+ wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual && it.isOn }
+ ?.layerStackSpace ?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(displayBounds.width() * 1 / 3, displayBounds.height() * 2 / 3), 200)
+ // Drag to a point on the lower left of the screen -- this will cause the divider to snap
+ // to the left- or bottom-side snap point, shrinking the "primary" test app.
+ dividerBar.drag(Point(displayBounds.width() * 1 / 4, displayBounds.height() * 3 / 4), 200)
wmHelper
.StateSyncBuilder()
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index d3fc91b65829..b3badd0bd51d 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -203,4 +203,11 @@ flag {
description: "Initialize GraphicBufferAllocater on ViewRootImpl init, to avoid blocking on init during buffer allocation, improving app launch latency."
bug: "389908734"
is_fixed_read_only: true
+}
+
+flag {
+ name: "bitmap_parcel_ashmem_as_immutable"
+ namespace: "system_performance"
+ description: "Whether to parcel implicit copies of bitmaps to ashmem as immutable"
+ bug: "400807118"
} \ No newline at end of file
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 27d4ac7cef4b..104ece6582f5 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -28,8 +28,18 @@
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTypes.h"
+#include "android/binder_parcel.h"
#include "android_nio_utils.h"
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
+namespace hwui_flags = com::android::graphics::hwui::flags;
+#else
+namespace hwui_flags {
+constexpr bool bitmap_parcel_ashmem_as_immutable() { return false; }
+}
+#endif
+
#define DEBUG_PARCEL 0
static jclass gBitmap_class;
@@ -841,6 +851,23 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
#endif
}
+// Returns whether this bitmap should be written to the parcel as mutable.
+static bool shouldParcelAsMutable(SkBitmap& bitmap, AParcel* parcel) {
+ // If the bitmap is immutable, then parcel as immutable.
+ if (bitmap.isImmutable()) {
+ return false;
+ }
+
+ if (!hwui_flags::bitmap_parcel_ashmem_as_immutable()) {
+ return true;
+ }
+
+ // If we're going to copy the bitmap to ashmem and write that to the parcel,
+ // then parcel as immutable, since we won't be mutating the bitmap after
+ // writing it to the parcel.
+ return !shouldUseAshmem(parcel, bitmap.computeByteSize());
+}
+
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density,
jobject parcel) {
#ifdef __linux__ // Only Linux support parcel
@@ -855,7 +882,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, j
auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
bitmapWrapper->getSkBitmap(&bitmap);
- p.writeInt32(!bitmap.isImmutable());
+ p.writeInt32(shouldParcelAsMutable(bitmap, p.get()));
p.writeInt32(bitmap.colorType());
p.writeInt32(bitmap.alphaType());
SkColorSpace* colorSpace = bitmap.colorSpace();
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index dc669a5eca73..aa8cbd1f0703 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -175,6 +175,8 @@ bool SkiaPipeline::setupMultiFrameCapture() {
if (stream->isValid()) {
mOpenMultiPicStream = std::move(stream);
mSerialContext.reset(new SkSharingSerialContext());
+ // passing the GrDirectContext to the SerialContext allows us to raster/serialize GPU images
+ mSerialContext->setDirectContext(mRenderThread.getGrContext());
SkSerialProcs procs;
procs.fImageProc = SkSharingSerialContext::serializeImage;
procs.fImageCtx = mSerialContext.get();
diff --git a/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml
new file mode 100644
index 000000000000..6b534aa9647d
--- /dev/null
+++ b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="34dp"
+ android:height="42dp"
+ android:viewportWidth="34"
+ android:viewportHeight="42">
+ <path
+ android:pathData="M0.856,17.569C0.887,19.083 1.004,20.593 1.206,22.094C2.166,28.584 5.804,35.937 15.774,41.089C16.171,41.293 16.61,41.4 17.056,41.4C17.503,41.4 17.942,41.293 18.339,41.089C28.309,35.936 31.947,28.583 32.907,22.093C33.109,20.593 33.226,19.083 33.256,17.569V8.605C33.257,7.919 33.046,7.25 32.652,6.688C32.259,6.127 31.703,5.7 31.059,5.467L18.191,0.8C17.458,0.534 16.655,0.534 15.922,0.8L3.054,5.467C2.41,5.7 1.854,6.127 1.461,6.688C1.067,7.25 0.856,7.919 0.856,8.605V17.569Z"
+ android:fillColor="#D1C2CB"/>
+ <path
+ android:pathData="M15.067,24.333V10.733H18.933V24.333H15.067ZM15.067,31.267V27.433H18.933V31.267H15.067Z"
+ android:fillColor="@color/settingslib_materialColorSurfaceContainerLowest"/>
+</vector> \ No newline at end of file
diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
index 54860d4af20f..deda2586c2e0 100644
--- a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
+++ b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
@@ -21,6 +21,7 @@
<enum name="low" value="1"/>
<enum name="medium" value="2"/>
<enum name="high" value="3"/>
+ <enum name="off" value="4"/>
</attr>
<attr name="buttonLevel" format="enum">
<enum name="generic" value="0"/>
diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
index 19181dd55852..abc458bc1e27 100644
--- a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
+++ b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
@@ -22,4 +22,5 @@
<color name="settingslib_expressive_color_status_level_medium">#FCBD00</color>
<!-- static palette red50 -->
<color name="settingslib_expressive_color_status_level_high">#DB372D</color>
+ <color name="settingslib_expressive_color_status_level_off">#D1C2CB</color>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
index 1f8cfb5e432e..eda281c07053 100644
--- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
+++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
@@ -40,7 +40,8 @@ class StatusBannerPreference @JvmOverloads constructor(
GENERIC,
LOW,
MEDIUM,
- HIGH
+ HIGH,
+ OFF
}
var iconLevel: BannerStatus = BannerStatus.GENERIC
set(value) {
@@ -87,6 +88,7 @@ class StatusBannerPreference @JvmOverloads constructor(
1 -> BannerStatus.LOW
2 -> BannerStatus.MEDIUM
3 -> BannerStatus.HIGH
+ 4 -> BannerStatus.OFF
else -> BannerStatus.GENERIC
}
@@ -104,7 +106,10 @@ class StatusBannerPreference @JvmOverloads constructor(
}
(holder.findViewById(R.id.status_banner_button) as? MaterialButton)?.apply {
- setBackgroundColor(getBackgroundColor(buttonLevel))
+ setBackgroundColor(
+ if (buttonLevel == BannerStatus.OFF) getBackgroundColor(BannerStatus.GENERIC)
+ else getBackgroundColor(buttonLevel)
+ )
text = buttonText
setOnClickListener(listener)
visibility = if (listener != null) View.VISIBLE else View.GONE
@@ -143,6 +148,11 @@ class StatusBannerPreference @JvmOverloads constructor(
R.color.settingslib_expressive_color_status_level_high
)
+ BannerStatus.OFF -> ContextCompat.getColor(
+ context,
+ R.color.settingslib_expressive_color_status_level_off
+ )
+
else -> ContextCompat.getColor(
context,
com.android.settingslib.widget.theme.R.color.settingslib_materialColorPrimary
@@ -167,6 +177,11 @@ class StatusBannerPreference @JvmOverloads constructor(
R.drawable.settingslib_expressive_icon_status_level_high
)
+ BannerStatus.OFF -> ContextCompat.getDrawable(
+ context,
+ R.drawable.settingslib_expressive_icon_status_level_off
+ )
+
else -> null
}
}
@@ -188,6 +203,7 @@ class StatusBannerPreference @JvmOverloads constructor(
R.drawable.settingslib_expressive_background_level_high
)
+ // GENERIC and OFF are using the same background drawable.
else -> ContextCompat.getDrawable(
context,
R.drawable.settingslib_expressive_background_generic
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 55f7317f25e4..758ad797f761 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -1015,6 +1015,10 @@
<uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" />
<uses-permission android:name="android.permission.READ_COLOR_ZONES" />
+ <!-- Permissions required for CTS test - CtsModernMediaProviderTests -->
+ <uses-permission android:name="com.android.providers.media.permission.ACCESS_OEM_METADATA" />
+ <uses-permission android:name="com.android.providers.media.permission.UPDATE_OEM_METADATA" />
+
<!-- Permission required for trade-in mode testing -->
<uses-permission android:name="android.permission.ENTER_TRADE_IN_MODE" />
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 1362ffebe107..86559fd557d6 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,22 +1,6 @@
{
- // Curious where your @Scenario tests are running?
- //
- // @Ignore: Will not run in any configuration
- //
- // @FlakyTest: Tests that don't block pre/postsubmit but are staged to run known failures.
- // Tests will run in postsubmit on sysui-e2e-staged suite.
- //
- //
- // @PlatinumTest: Marking your test with this annotation will put your tests in presubmit.
- // Please DO NOT annotate new or old tests with @PlatinumTest annotation
- // without discussing with mdb:android-platinum
- //
- // @Postsubmit: Do not use this annotation for e2e tests. This won't have any affect.
-
- // For all other e2e tests which are not platinum, they run in sysui-silver suite,that
- // primarily runs in postsubmit with an exception to e2e test related changes.
- // If you want to see one shot place to monitor all e2e tests, look for
- // sysui-e2e-staged suite.
+ // Test mappings for SystemUI unit tests.
+ // For e2e mappings, see go/sysui-e2e-test-mapping
// v2/android-virtual-infra/test_mapping/presubmit-avd
"presubmit": [
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 4693377654f8..436e92bc0efa 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -398,13 +398,6 @@ flag {
}
flag {
- name: "light_reveal_migration"
- namespace: "systemui"
- description: "Move LightRevealScrim to recommended architecture"
- bug: "281655028"
-}
-
-flag {
name: "theme_overlay_controller_wakefulness_deprecation"
namespace: "systemui"
description: "Replacing WakefulnessLifecycle by KeyguardTransitionInteractor in "
@@ -2136,3 +2129,14 @@ flag {
description: "Enables return animations for status bar chips"
bug: "202516970"
}
+
+flag {
+ name: "media_projection_grey_error_text"
+ namespace: "systemui"
+ description: "Set the error text color to grey when app sharing is hidden by the requesting app"
+ bug: "390624334"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index ca94482b9c5a..21ec89646f0f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -245,6 +245,17 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner
runner.onAnimationCancelled();
finishRunnable.run();
}
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted)
+ throws RemoteException {
+ // Notify the remote runner that the transition has been canceled if the transition
+ // was merged into another transition or aborted
+ synchronized (mFinishRunnables) {
+ mFinishRunnables.remove(transition);
+ }
+ runner.onAnimationCancelled();
+ }
};
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index 82e5f5bb6dc8..cd9fcefcdda3 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -127,6 +127,8 @@ import kotlin.math.min
*
* @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
* @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable
+ * isn't currently clickable and false otherwise.
*/
@Composable
fun Expandable(
@@ -140,6 +142,7 @@ fun Expandable(
// TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
// proven that the new implementation is robust.
useModifierBasedImplementation: Boolean = false,
+ defaultMinSize: Boolean = true,
transitionControllerFactory: ComposableControllerFactory? = null,
content: @Composable (Expandable) -> Unit,
) {
@@ -155,6 +158,7 @@ fun Expandable(
onClick,
interactionSource,
useModifierBasedImplementation,
+ defaultMinSize,
content,
)
}
@@ -182,6 +186,8 @@ fun Expandable(
*
* @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
* @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable
+ * isn't currently clickable and false otherwise.
*/
@Composable
fun Expandable(
@@ -192,6 +198,7 @@ fun Expandable(
// TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
// proven that the new implementation is robust.
useModifierBasedImplementation: Boolean = false,
+ defaultMinSize: Boolean = true,
content: @Composable (Expandable) -> Unit,
) {
val controller = controller as ExpandableControllerImpl
@@ -209,7 +216,12 @@ fun Expandable(
if (useModifierBasedImplementation) {
Box(modifier.expandable(controller, onClick, interactionSource)) {
- WrappedContent(controller.expandable, controller.contentColor, content)
+ WrappedContent(
+ controller.expandable,
+ controller.contentColor,
+ defaultMinSize = defaultMinSize,
+ content,
+ )
}
return
}
@@ -221,7 +233,7 @@ fun Expandable(
val wrappedContent =
remember(content) {
movableContentOf { expandable: Expandable ->
- WrappedContent(expandable, contentColor, content)
+ WrappedContent(expandable, contentColor, defaultMinSize = defaultMinSize, content)
}
}
@@ -306,21 +318,24 @@ fun Expandable(
private fun WrappedContent(
expandable: Expandable,
contentColor: Color,
+ defaultMinSize: Boolean,
content: @Composable (Expandable) -> Unit,
) {
val minSizeContent =
@Composable {
- // We make sure that the content itself (wrapped by the background) is at least 40.dp,
- // which is the same as the M3 buttons. This applies even if onClick is null, to make it
- // easier to write expandables that are sometimes clickable and sometimes not. There
- // shouldn't be any Expandable smaller than 40dp because if the expandable is not
- // clickable directly, then something in its content should be (and with a size >=
- // 40dp).
- val minSize = 40.dp
- Box(
- Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
- contentAlignment = Alignment.Center,
- ) {
+ if (defaultMinSize) {
+ // We make sure that the content itself (wrapped by the background) is at
+ // least 40.dp, which is the same as the M3 buttons. This applies even if
+ // onClick is null, to make it easier to write expandables that are
+ // sometimes clickable and sometimes not.
+ val minSize = 40.dp
+ Box(
+ modifier = Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+ contentAlignment = Alignment.Center,
+ ) {
+ content(expandable)
+ }
+ } else {
content(expandable)
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index 2ea9c487c27c..362748ec71b0 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -112,6 +112,16 @@ interface NestedDraggable {
interface Controller {
/**
+ * Whether this controller is ready to drag. [onDrag] will be called only if this returns
+ * `true`, and any drag event will be ignored until then.
+ *
+ * This can for instance be used to wait for the content we are dragging to to be composed
+ * before actually dragging, reducing perceivable jank at the beginning of a drag.
+ */
+ val isReadyToDrag: Boolean
+ get() = true
+
+ /**
* Whether drags that were started from nested scrolls should be automatically
* [stopped][onDragStopped] as soon as they don't consume the entire `delta` passed to
* [onDrag].
@@ -274,6 +284,9 @@ private class NestedDraggableNode(
/** The pointers currently down, in order of which they were done and mapping to their type. */
private val pointersDown = linkedMapOf<PointerId, PointerType>()
+ /** Whether the next drag event should be ignored. */
+ private var ignoreNextDrag = false
+
init {
delegate(nestedScrollModifierNode(this, nestedScrollDispatcher))
}
@@ -426,6 +439,7 @@ private class NestedDraggableNode(
velocityTracker: VelocityTracker,
) {
velocityTracker.addPointerInputChange(change)
+ if (shouldIgnoreDrag(controller)) return
scrollWithOverscroll(delta.toOffset()) { deltaFromOverscroll ->
scrollWithNestedScroll(deltaFromOverscroll) { deltaFromNestedScroll ->
@@ -434,6 +448,23 @@ private class NestedDraggableNode(
}
}
+ private fun shouldIgnoreDrag(controller: NestedDraggable.Controller): Boolean {
+ return when {
+ !controller.isReadyToDrag -> {
+ // The controller is not ready yet, so we are waiting for an expensive frame to be
+ // composed. We should ignore this drag and the next one, given that the first delta
+ // after an expensive frame will be large.
+ ignoreNextDrag = true
+ true
+ }
+ ignoreNextDrag -> {
+ ignoreNextDrag = false
+ true
+ }
+ else -> false
+ }
+ }
+
private fun onDragStopped(controller: NestedDraggable.Controller, velocity: Velocity) {
// We launch in the scope of the dispatcher so that the fling is not cancelled if this node
// is removed right after onDragStopped() is called.
@@ -617,6 +648,8 @@ private class NestedDraggableNode(
}
private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset {
+ if (shouldIgnoreDrag(controller.controller)) return offset
+
return scrollWithOverscroll(offset) { delta ->
val available = delta.toFloat()
val consumed = controller.controller.onDrag(available)
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index b247993de4e4..b31617369cdb 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -972,6 +972,36 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
}
@Test
+ fun isReadyToDrag() {
+ var isReadyToDrag by mutableStateOf(false)
+ val draggable = TestDraggable(isReadyToDrag = { isReadyToDrag })
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+ }
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy((touchSlop + 10f).toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragDelta).isEqualTo(0f)
+
+ rule.onRoot().performTouchInput { moveBy(20f.toOffset()) }
+ assertThat(draggable.onDragDelta).isEqualTo(0f)
+
+ // Flag as ready to drag. We still ignore the next drag after that.
+ isReadyToDrag = true
+ rule.onRoot().performTouchInput { moveBy(30f.toOffset()) }
+ assertThat(draggable.onDragDelta).isEqualTo(0f)
+
+ // Now we drag.
+ rule.onRoot().performTouchInput { moveBy(40f.toOffset()) }
+ assertThat(draggable.onDragDelta).isEqualTo(40f)
+ }
+
+ @Test
fun consumeNestedPreScroll() {
var consumeNestedPreScroll by mutableStateOf(false)
val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll })
@@ -1060,6 +1090,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
},
private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true },
private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false },
+ private val isReadyToDrag: () -> Boolean = { true },
private val autoStopNestedDrags: Boolean = false,
) : NestedDraggable {
var shouldStartDrag = true
@@ -1092,6 +1123,9 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
return object : NestedDraggable.Controller {
override val autoStopNestedDrags: Boolean = this@TestDraggable.autoStopNestedDrags
+ override val isReadyToDrag: Boolean
+ get() = isReadyToDrag()
+
override fun onDrag(delta: Float): Float {
onDragCalled = true
onDragDelta += delta
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 3150e94908cd..2b8fe39c4870 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -47,6 +47,7 @@ import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -104,7 +105,9 @@ val sceneTransitionsV2 = transitions {
fade(Communal.Elements.Grid)
fade(Communal.Elements.IndicationArea)
fade(Communal.Elements.LockIcon)
- fade(Communal.Elements.StatusBar)
+ if (!Flags.glanceableHubV2()) {
+ fade(Communal.Elements.StatusBar)
+ }
}
timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
}
@@ -131,7 +134,9 @@ val sceneTransitions = transitions {
fade(Communal.Elements.Grid)
fade(Communal.Elements.IndicationArea)
fade(Communal.Elements.LockIcon)
- fade(Communal.Elements.StatusBar)
+ if (!Flags.glanceableHubV2()) {
+ fade(Communal.Elements.StatusBar)
+ }
}
timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 2d03e2bcdd19..0181928317e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -29,6 +29,7 @@ import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.android.compose.animation.scene.ContentScope
+import com.android.systemui.Flags
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
@@ -70,8 +71,10 @@ constructor(
content = {
Box(modifier = Modifier.fillMaxSize()) {
with(communalPopupSection) { Popup() }
- with(ambientStatusBarSection) {
- AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f))
+ if (!Flags.glanceableHubV2()) {
+ with(ambientStatusBarSection) {
+ AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f))
+ }
}
CommunalHub(
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 7782705d4c61..336f9e1ad6e3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@@ -81,6 +82,7 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.flags.QsInCompose
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
@@ -388,6 +390,7 @@ private fun NewChangesDot(modifier: Modifier = Modifier) {
}
/** A larger button with an icon, some text and an optional dot (to indicate new changes). */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun TextButton(
icon: Icon,
@@ -422,10 +425,13 @@ private fun TextButton(
Text(
text,
Modifier.weight(1f),
- style = MaterialTheme.typography.bodyMedium,
- // TODO(b/242040009): Remove this letter spacing. We should only use the M3 text
- // styles without modifying them.
- letterSpacing = 0.01.em,
+ style =
+ if (QsInCompose.isEnabled) {
+ MaterialTheme.typography.labelLarge
+ } else {
+ MaterialTheme.typography.bodyMedium
+ },
+ letterSpacing = if (QsInCompose.isEnabled) 0.em else 0.01.em,
color = colorAttr(R.attr.onShadeInactiveVariant),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 4b3ebc2bd53d..da54cb8e4679 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -58,6 +58,7 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.progressBarRangeInfo
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.setProgress
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
@@ -106,7 +107,10 @@ fun VolumeSlider(
return
}
- Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) {
+ Column(
+ modifier = modifier.animateContentSize().semantics(true) {},
+ verticalArrangement = Arrangement.Top,
+ ) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth().height(40.dp),
@@ -123,7 +127,7 @@ fun VolumeSlider(
text = state.label,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
- modifier = Modifier.weight(1f),
+ modifier = Modifier.weight(1f).clearAndSetSemantics {},
)
button?.invoke()
}
@@ -134,12 +138,11 @@ fun VolumeSlider(
onValueChanged = onValueChange,
onValueChangeFinished = { onValueChangeFinished?.invoke() },
isEnabled = state.isEnabled,
- stepDistance = state.a11yStep,
+ stepDistance = state.step,
accessibilityParams =
AccessibilityParams(
- label = state.label,
- disabledMessage = state.disabledMessage,
- currentStateDescription = state.a11yStateDescription,
+ contentDescription = state.a11yContentDescription,
+ stateDescription = state.a11yStateDescription,
),
haptics =
hapticsViewModelFactory?.let {
@@ -169,7 +172,7 @@ fun VolumeSlider(
text = disabledMessage,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.labelSmall,
- modifier = Modifier.basicMarquee(),
+ modifier = Modifier.basicMarquee().clearAndSetSemantics {},
)
}
}
@@ -229,7 +232,7 @@ private fun LegacyVolumeSlider(
}
val newValue =
- (value + targetDirection * state.a11yStep).coerceIn(
+ (value + targetDirection * state.step).coerceIn(
state.valueRange.start,
state.valueRange.endInclusive,
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index b04d89d8160f..0b0df06c2015 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -20,6 +20,7 @@ import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.runtime.withFrameNanos
import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -47,6 +48,11 @@ internal fun CoroutineScope.animateContent(
oneOffAnimation.animatable = it
}
+ if (layoutState.deferTransitionProgress) {
+ // Defer the animation by one frame so that the transition progress is changed only when
+ // the expensive first composition frame is done.
+ withFrameNanos {}
+ }
animatable.animateTo(targetProgress, animationSpec, initialVelocity)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 024ca22069ae..36eafa400090 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -290,6 +290,15 @@ private class DragControllerImpl(
val isDrivingTransition: Boolean
get() = layoutState.transitionState == swipeAnimation.contentTransition
+ override val isReadyToDrag: Boolean
+ get() {
+ return !layoutState.deferTransitionProgress ||
+ with(draggableHandler.layoutImpl.elementStateScope) {
+ swipeAnimation.fromContent.targetSize() != null &&
+ swipeAnimation.toContent.targetSize() != null
+ }
+ }
+
init {
check(!isDrivingTransition) { "Multiple controllers with the same SwipeTransition" }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 4da83c3a6fc9..a8b676d4ee45 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -462,7 +462,9 @@ internal class SceneTransitionLayoutImpl(
// swipes.
.swipeToScene(horizontalDraggableHandler)
.swipeToScene(verticalDraggableHandler)
- .then(LayoutElement(layoutImpl = this))
+ .then(
+ LayoutElement(layoutImpl = this, transitionState = this.state.transitionState)
+ )
) {
LookaheadScope {
if (_lookaheadScope == null) {
@@ -623,23 +625,28 @@ internal class SceneTransitionLayoutImpl(
@VisibleForTesting internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays
}
-private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutImpl) :
- ModifierNodeElement<LayoutNode>() {
- override fun create(): LayoutNode = LayoutNode(layoutImpl)
+private data class LayoutElement(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ private val transitionState: TransitionState,
+) : ModifierNodeElement<LayoutNode>() {
+ override fun create(): LayoutNode = LayoutNode(layoutImpl, transitionState)
override fun update(node: LayoutNode) {
node.layoutImpl = layoutImpl
+ node.transitionState = transitionState
}
}
-private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
- Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode {
+private class LayoutNode(
+ var layoutImpl: SceneTransitionLayoutImpl,
+ var transitionState: TransitionState,
+) : Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode {
override fun onRemeasured(size: IntSize) {
layoutImpl.lastSize = size
}
override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
- return layoutImpl.state.isTransitioning()
+ return transitionState is TransitionState.Transition.ChangeScene
}
@ExperimentalComposeUiApi
@@ -652,8 +659,7 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
val width: Int
val height: Int
- val transition =
- layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeScene
+ val transition = transitionState as? TransitionState.Transition.ChangeScene
if (transition == null) {
width = placeable.width
height = placeable.height
@@ -662,6 +668,9 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
val fromSize = layoutImpl.scene(transition.fromScene).targetSize
val toSize = layoutImpl.scene(transition.toScene).targetSize
+ check(fromSize != Element.SizeUnspecified) { "fromSize is unspecified " }
+ check(toSize != Element.SizeUnspecified) { "toSize is unspecified" }
+
// Optimization: make sure we don't read state.progress if fromSize ==
// toSize to avoid running this code every frame when the layout size does
// not change.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 56e8c458ad67..4e28dd569f21 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -234,6 +234,10 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState
* `from` overlay by `to` overlay.
* @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other
* [SceneTransitionLayoutState]s.
+ * @param deferTransitionProgress whether we should wait for the first composition to be done before
+ * changing the progress of a transition. This can help reduce perceivable jank at the start of a
+ * transition in case the first composition of a content takes a lot of time and we are going to
+ * miss that first frame.
*/
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
@@ -246,6 +250,9 @@ fun MutableSceneTransitionLayoutState(
canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
onTransitionStart: (TransitionState.Transition) -> Unit = {},
onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+
+ // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released.
+ deferTransitionProgress: Boolean = false,
): MutableSceneTransitionLayoutState {
return MutableSceneTransitionLayoutStateImpl(
initialScene,
@@ -258,6 +265,7 @@ fun MutableSceneTransitionLayoutState(
canReplaceOverlay,
onTransitionStart,
onTransitionEnd,
+ deferTransitionProgress,
)
}
@@ -272,6 +280,9 @@ fun rememberMutableSceneTransitionLayoutState(
canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
onTransitionStart: (TransitionState.Transition) -> Unit = {},
onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+
+ // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released.
+ deferTransitionProgress: Boolean = false,
): MutableSceneTransitionLayoutState {
val motionScheme = MaterialTheme.motionScheme
val layoutState = remember {
@@ -286,6 +297,7 @@ fun rememberMutableSceneTransitionLayoutState(
canReplaceOverlay = canReplaceOverlay,
onTransitionStart = onTransitionStart,
onTransitionEnd = onTransitionEnd,
+ deferTransitionProgress = deferTransitionProgress,
)
}
@@ -298,6 +310,7 @@ fun rememberMutableSceneTransitionLayoutState(
layoutState.canReplaceOverlay = canReplaceOverlay
layoutState.onTransitionStart = onTransitionStart
layoutState.onTransitionEnd = onTransitionEnd
+ layoutState.deferTransitionProgress = deferTransitionProgress
}
return layoutState
}
@@ -317,6 +330,8 @@ internal class MutableSceneTransitionLayoutStateImpl(
},
internal var onTransitionStart: (TransitionState.Transition) -> Unit = {},
internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+ // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released.
+ internal var deferTransitionProgress: Boolean = false,
) : MutableSceneTransitionLayoutState {
private val creationThread: Thread = Thread.currentThread()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
index 2d2a81542f84..5d4232d8a8b7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
@@ -38,7 +38,7 @@ internal class ElementStateScopeImpl(private val layoutImpl: SceneTransitionLayo
}
override fun ContentKey.targetSize(): IntSize? {
- return layoutImpl.content(this).targetSize.takeIf { it != IntSize.Zero }
+ return layoutImpl.content(this).targetSize.takeIf { it != Element.SizeUnspecified }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 90bf92ae1dd0..7492f3737edc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -93,9 +93,10 @@ internal sealed class Content(
val containerState = ContainerState()
// Important: All fields in this class should be backed by State given that contents are updated
- // directly during composition, outside of a SideEffect.
+ // directly during composition, outside of a SideEffect, or are observed during composition,
+ // layout or drawing.
var content by mutableStateOf(content)
- var targetSize by mutableStateOf(IntSize.Zero)
+ var targetSize by mutableStateOf(Element.SizeUnspecified)
var userActions by mutableStateOf(actions)
var zIndex by mutableFloatStateOf(zIndex)
@@ -212,9 +213,17 @@ private class ContentNode(
return if (isElevationPossible) delegate(ContainerNode(content.containerState)) else null
}
+ override fun onDetach() {
+ this.content.targetSize = Element.SizeUnspecified
+ }
+
fun update(content: Content, isElevationPossible: Boolean, isInvisible: Boolean) {
- if (content != this.content || isElevationPossible != this.isElevationPossible) {
+ if (content != this.content) {
+ this.content.targetSize = Element.SizeUnspecified
this.content = content
+ }
+
+ if (content != this.content || isElevationPossible != this.isElevationPossible) {
this.isElevationPossible = isElevationPossible
containerDelegate?.let { undelegate(it) }
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json
index 5dbb01338090..770f85969ed1 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json
@@ -76,7 +76,7 @@
},
{
"x": 5.2,
- "y": 5.6
+ "y": 5.2
},
{
"x": 5.2,
@@ -326,11 +326,11 @@
},
{
"width": 150,
- "height": 293.2
+ "height": 292.8
},
{
"width": 150,
- "height": 293.2
+ "height": 292.8
},
{
"width": 150,
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json
index 1543d186ea03..6b7de56c1da6 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json
@@ -81,8 +81,8 @@
"y": 5.2
},
{
- "x": 5.6,
- "y": 6.8
+ "x": 5.2,
+ "y": 5.2
},
{
"x": 5.2,
@@ -344,7 +344,7 @@
},
{
"width": 150,
- "height": 294
+ "height": 292.4
},
{
"width": 150,
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json
index 115483cf4013..015df8fd02fb 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json
@@ -84,8 +84,8 @@
"y": 5.2
},
{
- "x": 5.6,
- "y": 6.4
+ "x": 5.2,
+ "y": 5.2
},
{
"x": 5.2,
@@ -259,7 +259,7 @@
},
{
"width": 150,
- "height": 290
+ "height": 288.8
},
{
"width": 150,
@@ -279,34 +279,34 @@
},
{
"width": 150,
- "height": 223.6
+ "height": 224
},
{
"width": 150,
- "height": 182.8
+ "height": 183.2
},
{
"width": 150,
- "height": 141.2
+ "height": 141.6
},
{
"width": 150,
- "height": 104
+ "height": 104.4
},
{
- "width": 147.6,
- "height": 74
+ "width": 148,
+ "height": 74.4
},
{
"width": 139.6,
- "height": 50.8
+ "height": 51.2
},
{
"width": 133.6,
- "height": 32
+ "height": 32.4
},
{
- "width": 129.2,
+ "width": 129.6,
"height": 15.6
},
{
@@ -409,19 +409,19 @@
1,
1,
1,
- 0.99479187,
- 0.8575029,
- 0.65572864,
- 0.4691311,
- 0.3215357,
- 0.21380007,
- 0.13896108,
- 0.0887118,
- 0.05580789,
- 0.03467691,
- 0.021318138,
- 0.0129826665,
- 0.007839739,
+ 0.99532944,
+ 0.8594856,
+ 0.65783304,
+ 0.47089338,
+ 0.32286334,
+ 0.21474117,
+ 0.139602,
+ 0.089136004,
+ 0.056082606,
+ 0.03485179,
+ 0.02142787,
+ 0.013050735,
+ 0.007881463,
0,
0,
0,
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json
index f44d4cd7c14e..5ac06217d161 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json
@@ -72,7 +72,7 @@
},
{
"x": 5.2,
- "y": 5.6
+ "y": 5.2
},
{
"x": 5.2,
@@ -306,11 +306,11 @@
},
{
"width": 150,
- "height": 293.2
+ "height": 292.8
},
{
"width": 150,
- "height": 293.2
+ "height": 292.8
},
{
"width": 150,
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json
index 9b68c71a7a34..1cae67b3eba7 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json
@@ -79,8 +79,8 @@
"y": 5.2
},
{
- "x": 5.6,
- "y": 6.8
+ "x": 5.2,
+ "y": 5.2
},
{
"x": 5.2,
@@ -334,7 +334,7 @@
},
{
"width": 150,
- "height": 294
+ "height": 292.4
},
{
"width": 150,
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json
index 86805bd6ff29..ca87bc28c45d 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json
@@ -83,8 +83,8 @@
"y": 5.2
},
{
- "x": 5.6,
- "y": 6.4
+ "x": 5.2,
+ "y": 5.2
},
{
"x": 5.2,
@@ -254,7 +254,7 @@
},
{
"width": 150,
- "height": 290
+ "height": 288.8
},
{
"width": 150,
@@ -274,27 +274,27 @@
},
{
"width": 150,
- "height": 223.6
+ "height": 224
},
{
"width": 150,
- "height": 182.8
+ "height": 183.2
},
{
"width": 150,
- "height": 141.2
+ "height": 141.6
},
{
"width": 150,
- "height": 104
+ "height": 104.4
},
{
"width": 150,
- "height": 72
+ "height": 72.4
},
{
"width": 150,
- "height": 46
+ "height": 46.4
},
{
"width": 150,
@@ -400,19 +400,19 @@
1,
1,
1,
- 0.99479187,
- 0.8575029,
- 0.65572864,
- 0.4691311,
- 0.3215357,
- 0.21380007,
- 0.13896108,
- 0.0887118,
- 0.05580789,
- 0.03467691,
- 0.021318138,
- 0.0129826665,
- 0.007839739,
+ 0.99532944,
+ 0.8594856,
+ 0.65783304,
+ 0.47089338,
+ 0.32286334,
+ 0.21474117,
+ 0.139602,
+ 0.089136004,
+ 0.056082606,
+ 0.03485179,
+ 0.02142787,
+ 0.013050735,
+ 0.007881463,
0,
0,
0,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt
index e4a87ba3a26b..7d9a32b3948a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt
@@ -58,17 +58,24 @@ class ElementStateAccessTest {
assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(1f, 1f))
}
+ at(16) {
+ val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode()
+ assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.75f)
+ assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.75f, 0.75f))
+ }
+
at(32) {
val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode()
assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.5f)
assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.5f, 0.5f))
}
- at(64) {
+ at(48) {
val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode()
- assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0f)
- assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0f, 0f))
+ assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.25f)
+ assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.25f, 0.25f))
}
+
after { onElement(TestElements.Foo).assertDoesNotExist() }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
index 6c7783ae44e1..28b9e733be94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.display.data.repository
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 0f631509bfba..d9990ba8e9ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -275,14 +275,14 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
"abc/.def", /* validateActivity */ true, /* enableSetting */true,
/* enableOnLockScreen */ true);
- mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
+ mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0,
UserHandle.USER_CURRENT);
- mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
+ mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0,
UserHandle.USER_CURRENT);
- mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
+ mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1,
UserHandle.USER_CURRENT);
- mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
+ mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1,
UserHandle.USER_CURRENT);
// Once from setup + twice from this function
verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
@@ -297,14 +297,14 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
- mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
+ mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0,
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
assertThat(mController.isAllowedOnLockScreen()).isTrue();
assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
- mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
+ mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1,
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
index a13b8647e170..101874807474 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
@@ -24,6 +24,7 @@ import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -126,7 +127,7 @@ class BundleEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isBubble() {
- assertThat(underTest.isBubbleCapable).isFalse()
+ assertThat(underTest.isBubble).isFalse()
}
@Test
@@ -152,4 +153,10 @@ class BundleEntryAdapterTest : SysuiTestCase() {
fun canShowFullScreen() {
assertThat(underTest.isFullScreenCapable()).isFalse()
}
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getPeopleNotificationType() {
+ assertThat(underTest.getPeopleNotificationType()).isEqualTo(TYPE_NON_PERSON)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
index 1018ded72783..7449064bc65b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.mockNotificationActivityStarter
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.entryAdapterFactory
import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager
@@ -109,7 +110,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getRow_adapter() {
- val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val row = mock(ExpandableNotificationRow::class.java)
val notification: Notification =
Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
@@ -127,7 +128,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isGroupRoot_adapter_groupSummary() {
- val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val row = mock(ExpandableNotificationRow::class.java)
val notification: Notification =
Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
@@ -174,7 +175,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isClearable_adapter() {
- val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val row = mock(ExpandableNotificationRow::class.java)
val notification: Notification =
Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
@@ -192,7 +193,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getSummarization_adapter() {
- val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val row = mock(ExpandableNotificationRow::class.java)
val notification: Notification =
Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
@@ -212,7 +213,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getIcons_adapter() {
- val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val row = mock(ExpandableNotificationRow::class.java)
val notification: Notification =
Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
@@ -257,7 +258,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun canDragAndDrop() {
- val pi = Mockito.mock(PendingIntent::class.java)
+ val pi = mock(PendingIntent::class.java)
Mockito.`when`(pi.isActivity).thenReturn(true)
val notification: Notification =
Notification.Builder(mContext, "")
@@ -283,7 +284,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
val entry = NotificationEntryBuilder().setNotification(notification).build()
underTest = factory.create(entry) as NotificationEntryAdapter
- assertThat(underTest.isBubbleCapable).isEqualTo(entry.isBubble)
+ assertThat(underTest.isBubble).isEqualTo(entry.isBubble)
}
@Test
@@ -335,11 +336,21 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getPeopleNotificationType() {
+ val entry = kosmos.msgStyleBubbleableFullPerson
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+
+ assertThat(underTest.peopleNotificationType).isEqualTo(TYPE_FULL_PERSON)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun canShowFullScreen() {
val notification: Notification =
Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
- .setFullScreenIntent(Mockito.mock(PendingIntent::class.java), true)
+ .setFullScreenIntent(mock(PendingIntent::class.java), true)
.build()
val entry =
@@ -354,6 +365,22 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun onDragSuccess() {
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .addAction(mock(Notification.Action::class.java))
+ .build()
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+
+ underTest.onDragSuccess()
+ verify(kosmos.mockNotificationActivityStarter).onDragSuccess(entry)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun onNotificationBubbleIconClicked() {
val notification: Notification =
Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
@@ -372,7 +399,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
val notification: Notification =
Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
- .addAction(Mockito.mock(Notification.Action::class.java))
+ .addAction(mock(Notification.Action::class.java))
.build()
val entry = NotificationEntryBuilder().setNotification(notification).build()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
index 7fa157fa7cb3..ba2d40ba6a0d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
@@ -27,7 +27,6 @@ import android.testing.TestableLooper.RunWithLooper
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.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
@@ -35,10 +34,10 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.core.StatusBarRootModernization
-import com.android.systemui.statusbar.notification.buildNotificationEntry
-import com.android.systemui.statusbar.notification.buildOngoingCallEntry
-import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry
import com.android.systemui.statusbar.notification.collection.buildEntry
+import com.android.systemui.statusbar.notification.collection.buildNotificationEntry
+import com.android.systemui.statusbar.notification.collection.buildOngoingCallEntry
+import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifPipeline
@@ -49,7 +48,6 @@ import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernizat
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index 893c17998a17..4c099b305332 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -455,6 +455,7 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
assertThat(content).isNotNull()
assertThat(content?.style).isEqualTo(Style.Call)
+ assertThat(content?.title).isEqualTo(TEST_PERSON_NAME)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
index 6926677feda0..6192399c522b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
@@ -28,9 +28,9 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.core.StatusBarRootModernization
-import com.android.systemui.statusbar.notification.buildNotificationEntry
-import com.android.systemui.statusbar.notification.buildOngoingCallEntry
-import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry
+import com.android.systemui.statusbar.notification.collection.buildNotificationEntry
+import com.android.systemui.statusbar.notification.collection.buildOngoingCallEntry
+import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry
import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index d306a5bea433..0d453352e882 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -51,6 +51,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -318,7 +319,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
val notification = Notification.Builder(mContext).build()
val sbn =
SbnBuilder().setNotification(notification).setUser(UserHandle.of(USER_ALL)).build()
- whenever(view.entry)
+ whenever(view.entryLegacy)
.thenReturn(
NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build()
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 9fdfca14a5b2..ce120c51db6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -38,7 +38,9 @@ import android.view.ViewGroup;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -54,6 +56,7 @@ import org.mockito.Mockito;
@SmallTest
public class NotificationMenuRowTest extends LeakCheckedTest {
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private ExpandableNotificationRow mRow;
private View mView;
private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@@ -66,6 +69,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
NotificationEntry entry = new NotificationEntryBuilder().build();
when(mRow.getEntry()).thenReturn(entry);
+ EntryAdapter entryAdapter = mKosmos.getEntryAdapterFactory().create(entry);
+ when(mRow.getEntryAdapter()).thenReturn(entryAdapter);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 41cca19346f0..6ec1f919fb47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -129,7 +129,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
whenever(notificationRow.key).thenReturn("key")
whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
- whenever(notificationRow.entry).thenReturn(notificationEntry)
+ whenever(notificationRow.entryLegacy).thenReturn(notificationEntry)
whenever(notificationRow.entryAdapter).thenReturn(notificationEntryAdapter)
whenever(notificationRow.roundableState)
.thenReturn(RoundableState(notificationRow, notificationRow, 0f))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 46430afecbb1..1f37291efbab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -790,6 +790,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
fun animateToGlanceableHub_affectsAlpha() =
testScope.runTest {
try {
@@ -809,6 +810,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() =
testScope.runTest {
try {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index c23e0e733b41..1cc291199531 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -221,6 +221,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
+ when(enr.getEntryLegacy()).thenReturn(enrEntry);
when(enr.getEntryAdapter()).thenReturn(enrEntryAdapter);
when(enr.isChildInGroup()).thenReturn(true);
when(enr.areChildrenExpanded()).thenReturn(false);
@@ -251,6 +252,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
+ when(enr.getEntryLegacy()).thenReturn(enrEntry);
when(enr.isChildInGroup()).thenReturn(true);
when(enr.areChildrenExpanded()).thenReturn(true);
@@ -277,6 +279,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
+ when(enr.getEntryLegacy()).thenReturn(enrEntry);
when(enr.isChildInGroup()).thenReturn(false);
when(enr.isPinned()).thenReturn(false);
when(enr.isExpanded()).thenReturn(false);
@@ -305,6 +308,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
+ when(enr.getEntryLegacy()).thenReturn(enrEntry);
when(enr.isChildInGroup()).thenReturn(false);
when(enr.isPinned()).thenReturn(false);
when(enr.isExpanded()).thenReturn(true);
@@ -333,6 +337,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
+ when(enr.getEntryLegacy()).thenReturn(enrEntry);
when(enr.isChildInGroup()).thenReturn(false);
when(enr.isPinned()).thenReturn(true);
when(enr.isPinnedAndExpanded()).thenReturn(false);
@@ -361,6 +366,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
+ when(enr.getEntryLegacy()).thenReturn(enrEntry);
when(enr.isChildInGroup()).thenReturn(false);
when(enr.isPinned()).thenReturn(true);
when(enr.isPinnedAndExpanded()).thenReturn(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index bafa8cf05a7f..da5622a2a8da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.user.data.repository
+import android.app.admin.DevicePolicyManager
import android.app.admin.devicePolicyManager
import android.content.Intent
+import android.content.applicationContext
import android.content.pm.UserInfo
import android.internal.statusbar.fakeStatusBarService
import android.os.UserHandle
@@ -77,10 +79,7 @@ class UserRepositoryImplTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
tracker = FakeUserTracker()
- context.orCreateTestableResources.addOverride(
- R.bool.config_userSwitchingMustGoThroughLoginScreen,
- false,
- )
+ setUserSwitchingMustGoThroughLoginScreen(false)
}
@Test
@@ -308,6 +307,117 @@ class UserRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun isSecondaryUserLogoutEnabled_secondaryLogoutDisabled_alwaysFalse() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
+ setSecondaryUserLogoutEnabled(false)
+ setUpUsers(count = 2, selectedIndex = 0)
+ tracker.onProfileChanged()
+
+ val secondaryUserLogoutEnabled by
+ collectLastValue(underTest.isSecondaryUserLogoutEnabled)
+
+ assertThat(secondaryUserLogoutEnabled).isFalse()
+
+ setUpUsers(count = 2, selectedIndex = 1)
+ tracker.onProfileChanged()
+ assertThat(secondaryUserLogoutEnabled).isFalse()
+ }
+
+ @Test
+ fun isSecondaryUserLogoutEnabled_secondaryLogoutEnabled_NullLogoutUser_alwaysFalse() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ mockLogoutUser(LogoutUserResult.NONE)
+ setSecondaryUserLogoutEnabled(true)
+ setUpUsers(count = 2, selectedIndex = 0)
+ tracker.onProfileChanged()
+
+ val secondaryUserLogoutEnabled by
+ collectLastValue(underTest.isSecondaryUserLogoutEnabled)
+
+ assertThat(secondaryUserLogoutEnabled).isFalse()
+
+ setUpUsers(count = 2, selectedIndex = 1)
+ tracker.onProfileChanged()
+ assertThat(secondaryUserLogoutEnabled).isFalse()
+ }
+
+ @Test
+ fun isSecondaryUserLogoutEnabled_secondaryLogoutEnabled_NonSystemLogoutUser_trueWhenNonSystem() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
+ setSecondaryUserLogoutEnabled(true)
+ setUpUsers(count = 2, selectedIndex = 0)
+ tracker.onProfileChanged()
+
+ val secondaryUserLogoutEnabled by
+ collectLastValue(underTest.isSecondaryUserLogoutEnabled)
+
+ assertThat(secondaryUserLogoutEnabled).isFalse()
+
+ setUpUsers(count = 2, selectedIndex = 1)
+ tracker.onProfileChanged()
+ assertThat(secondaryUserLogoutEnabled).isTrue()
+ }
+
+ @Test
+ fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenDisabled_alwaysFalse() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
+ setUserSwitchingMustGoThroughLoginScreen(false)
+ setUpUsers(count = 2, selectedIndex = 0)
+ tracker.onProfileChanged()
+
+ val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled)
+
+ assertThat(logoutToSystemUserEnabled).isFalse()
+
+ setUpUsers(count = 2, selectedIndex = 1)
+ tracker.onProfileChanged()
+ assertThat(logoutToSystemUserEnabled).isFalse()
+ }
+
+ @Test
+ fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenEnabled_NullLogoutUser_alwaysFalse() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ mockLogoutUser(LogoutUserResult.NONE)
+ setUserSwitchingMustGoThroughLoginScreen(true)
+ setUpUsers(count = 2, selectedIndex = 0)
+ tracker.onProfileChanged()
+
+ val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled)
+
+ assertThat(logoutToSystemUserEnabled).isFalse()
+
+ setUpUsers(count = 2, selectedIndex = 1)
+ tracker.onProfileChanged()
+ assertThat(logoutToSystemUserEnabled).isFalse()
+ }
+
+ @Test
+ fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenEnabled_NonSystemLogoutUser_trueWhenNonSystem() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
+ setUserSwitchingMustGoThroughLoginScreen(true)
+ setUpUsers(count = 2, selectedIndex = 0)
+ tracker.onProfileChanged()
+
+ val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled)
+
+ assertThat(logoutToSystemUserEnabled).isFalse()
+
+ setUpUsers(count = 2, selectedIndex = 1)
+ tracker.onProfileChanged()
+ assertThat(logoutToSystemUserEnabled).isTrue()
+ }
+
private fun createUserInfo(id: Int, isGuest: Boolean): UserInfo {
val flags = 0
return UserInfo(
@@ -354,6 +464,38 @@ class UserRepositoryImplTest : SysuiTestCase() {
assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
}
+ private fun setSecondaryUserLogoutEnabled(enabled: Boolean) {
+ whenever(devicePolicyManager.isLogoutEnabled).thenReturn(enabled)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ kosmos.applicationContext,
+ Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ )
+ }
+
+ private fun setUserSwitchingMustGoThroughLoginScreen(enabled: Boolean) {
+ context.orCreateTestableResources.addOverride(
+ R.bool.config_userSwitchingMustGoThroughLoginScreen,
+ enabled,
+ )
+ }
+
+ private fun mockLogoutUser(result: LogoutUserResult) {
+ when (result) {
+ LogoutUserResult.NONE -> {
+ whenever(devicePolicyManager.logoutUser).thenReturn(null)
+ }
+ LogoutUserResult.NON_SYSTEM_CURRENT -> {
+ whenever(devicePolicyManager.logoutUser).thenAnswer {
+ if (tracker.userHandle != UserHandle.SYSTEM) {
+ tracker.userHandle
+ } else {
+ null
+ }
+ }
+ }
+ }
+ }
+
private fun create(scope: CoroutineScope): UserRepositoryImpl {
return UserRepositoryImpl(
appContext = context,
@@ -373,4 +515,9 @@ class UserRepositoryImplTest : SysuiTestCase() {
companion object {
@JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate
}
+
+ private enum class LogoutUserResult {
+ NONE,
+ NON_SYSTEM_CURRENT,
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
index 04ab98889755..b1a3caf98f09 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.bluetooth.BluetoothDevice
+import android.graphics.drawable.TestStubDrawable
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -63,6 +64,12 @@ class AudioSharingStreamSliderViewModelTest : SysuiTestCase() {
assertThat(audioSharingSlider!!.label).isEqualTo("my headset 2")
assertThat(audioSharingSlider!!.icon)
- .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null))
+ .isEqualTo(
+ Icon.Loaded(
+ drawable = TestStubDrawable(),
+ res = R.drawable.ic_volume_media_bt,
+ contentDescription = null,
+ )
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
index 9e8cde3bc936..ffe8e923815f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.app.Flags
import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
+import android.graphics.drawable.TestStubDrawable
import android.media.AudioManager
import android.platform.test.annotations.EnableFlags
import android.service.notification.ZenPolicy
@@ -28,7 +29,6 @@ import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.collectLastValue
@@ -39,8 +39,6 @@ import com.android.systemui.statusbar.policy.data.repository.fakeZenModeReposito
import com.android.systemui.testKosmos
import com.android.systemui.volume.data.repository.audioSharingRepository
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -173,6 +171,12 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() {
assertThat(mediaSlider!!.label).isEqualTo("my headset 1")
assertThat(mediaSlider!!.icon)
- .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null))
+ .isEqualTo(
+ Icon.Loaded(
+ drawable = TestStubDrawable(),
+ res = R.drawable.ic_volume_media_bt,
+ contentDescription = null,
+ )
+ )
}
}
diff --git a/packages/SystemUI/res/color/brightness_slider_track.xml b/packages/SystemUI/res/color/brightness_slider_track.xml
new file mode 100644
index 000000000000..c6e9b65fb7e8
--- /dev/null
+++ b/packages/SystemUI/res/color/brightness_slider_track.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral2_500" android:lStar="40" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 88d3ecb3f423..d38da7b766c1 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -25,7 +25,7 @@
<shape>
<size android:height="@dimen/rounded_slider_track_width" />
<corners android:radius="@dimen/rounded_slider_track_corner_radius" />
- <solid android:color="@androidprv:color/customColorShadeInactive" />
+ <solid android:color="@color/brightness_slider_track" />
</shape>
</inset>
</item>
diff --git a/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml b/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml
new file mode 100644
index 000000000000..2173641376fa
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/hearing_device_ambient_icon_background"
+ android:insetTop="10dp"
+ android:insetBottom="10dp"/>
diff --git a/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml b/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml
new file mode 100644
index 000000000000..81ef3d7c11b7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="@androidprv:color/materialColorSurfaceContainerHighest" />
+ <corners
+ android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+ android:topLeftRadius="?android:attr/dialogCornerRadius"
+ android:bottomRightRadius="?android:attr/dialogCornerRadius"
+ android:topRightRadius="?android:attr/dialogCornerRadius" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
index 73704f823033..f17cc96329c6 100644
--- a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
+++ b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
@@ -21,7 +21,7 @@
android:height="2dp"
android:width="@dimen/rear_display_progress_width">
<shape android:shape="rectangle">
- <solid android:color="@androidprv:color/materialColorSurfaceContainer" />
+ <solid android:color="?android:attr/colorAccent"/>
</shape>
</item>
<item
@@ -29,4 +29,4 @@
android:gravity="center_vertical|fill_horizontal">
<com.android.systemui.util.RoundedCornerProgressDrawable android:drawable="@drawable/rear_display_dialog_seekbar_progress" />
</item>
-</layer-list> \ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
index fd409a5a8bb1..d3e9db15dfdd 100644
--- a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
+++ b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
@@ -36,7 +36,8 @@
android:padding="12dp"
android:contentDescription="@string/hearing_devices_ambient_unmute"
android:src="@drawable/ic_ambient_volume"
- android:tint="@androidprv:color/materialColorOnSurface" />
+ android:tint="@androidprv:color/materialColorOnSurface"
+ android:background="@drawable/hearing_device_ambient_icon_background"/>
<TextView
android:id="@+id/ambient_title"
android:layout_width="0dp"
@@ -56,7 +57,8 @@
android:padding="10dp"
android:contentDescription="@string/hearing_devices_ambient_expand_controls"
android:src="@drawable/ic_hearing_device_expand"
- android:tint="@androidprv:color/materialColorOnSurface" />
+ android:tint="@androidprv:color/materialColorOnSurface"
+ android:background="@drawable/hearing_device_ambient_expand_icon_background"/>
</LinearLayout>
<LinearLayout
android:id="@+id/ambient_control_container"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 15519ff37d0c..7c6a1b1bf63d 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -146,7 +146,8 @@
<color name="smart_reply_button_stroke">@*android:color/accent_device_default</color>
<!-- Magic Action colors -->
- <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurfaceVariant</color>
+ <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurface</color>
+ <color name="magic_action_button_stroke_color">@androidprv:color/materialColorOnSurface</color>
<!-- Biometric dialog colors -->
<color name="biometric_dialog_gray">#ff757575</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3fdb98b4e00b..b627bdf22a6c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1483,6 +1483,8 @@
<string name="media_projection_entry_app_permission_dialog_continue_entire_screen">Share screen</string>
<!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] -->
<string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string>
+ <!-- Explanation that the app requesting the projection does not support single app sharing[CHAR LIMIT=70] -->
+ <string name="media_projection_entry_app_permission_dialog_single_app_not_supported">Not supported by the app</string>
<!-- Title of the activity that allows users to select an app to share to a 1P/3P app [CHAR LIMIT=70] -->
<string name="media_projection_entry_share_app_selector_title">Choose app to share</string>
@@ -2558,6 +2560,9 @@
<!-- Button to edit the tile ordering of quick settings [CHAR LIMIT=60] -->
<string name="qs_edit">Edit</string>
+ <!-- Title for QS Edit mode screen [CHAR LIMIT=30] -->
+ <string name="qs_edit_tiles">Edit tiles</string>
+
<!-- SysUI Tuner: Options for how clock is displayed [CHAR LIMIT=NONE] -->
<string name="tuner_time">Time</string>
diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml
index 98e5cea0e78f..a7ad48d17abb 100644
--- a/packages/SystemUI/shared/res/values/bools.xml
+++ b/packages/SystemUI/shared/res/values/bools.xml
@@ -22,7 +22,4 @@
<resources>
<!-- Whether to add padding at the bottom of the complication clock -->
<bool name="dream_overlay_complication_clock_bottom_padding">false</bool>
-
- <!-- Whether to mark tasks that are present in the UI as perceptible tasks. -->
- <bool name="config_usePerceptibleTasks">false</bool>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 487d1ce2514e..b981f9837787 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -46,8 +46,6 @@ import android.view.Display;
import android.window.TaskSnapshot;
import com.android.internal.app.IVoiceInteractionManagerService;
-import com.android.server.am.Flags;
-import com.android.systemui.shared.R;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -324,14 +322,6 @@ public class ActivityManagerWrapper {
}
/**
- * Returns true if tasks with a presence in the UI should be marked as perceptible tasks.
- */
- public static boolean usePerceptibleTasks(Context context) {
- return Flags.perceptibleTasks()
- && context.getResources().getBoolean(R.bool.config_usePerceptibleTasks);
- }
-
- /**
* Returns true if the running task represents the home task
*/
public static boolean isHomeTask(RunningTaskInfo info) {
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 bd3dfe049587..8025d4b0c2ea 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -317,14 +317,14 @@ class MenuViewAppearance {
public static float avoidVerticalDisplayCutout(
float y, float menuHeight, Rect bounds, Rect cutout) {
if (cutout.top > y + menuHeight || cutout.bottom < y) {
- return y;
+ return clampVerticalPosition(y, menuHeight, bounds.top, bounds.bottom);
}
boolean topAvailable = cutout.top - bounds.top >= menuHeight;
boolean bottomAvailable = bounds.bottom - cutout.bottom >= menuHeight;
boolean topOrBottom;
if (!topAvailable && !bottomAvailable) {
- return y;
+ return clampVerticalPosition(y, menuHeight, bounds.top, bounds.bottom);
} else if (topAvailable && !bottomAvailable) {
topOrBottom = true;
} else if (!topAvailable && bottomAvailable) {
@@ -332,7 +332,16 @@ class MenuViewAppearance {
} else {
topOrBottom = y + menuHeight * 0.5f < cutout.centerY();
}
- return (topOrBottom) ? cutout.top - menuHeight : cutout.bottom;
+
+ float finalPosition = (topOrBottom) ? cutout.top - menuHeight : cutout.bottom;
+ return clampVerticalPosition(finalPosition, menuHeight, bounds.top, bounds.bottom);
+ }
+
+ private static float clampVerticalPosition(
+ float position, float height, float min, float max) {
+ position = Float.max(min + height / 2, position);
+ position = Float.min(max - height / 2, position);
+ return position;
}
boolean isMenuOnLeftSide() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 22d2aaf2a8e7..87e9784ddbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,7 +32,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.logging.KeyguardLogger
import com.android.settingslib.Utils
import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.dagger.SysUISingleton
@@ -43,6 +42,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LiftReveal
import com.android.systemui.statusbar.LightRevealEffect
@@ -196,7 +196,7 @@ constructor(
// This code path is not used if the KeyguardTransitionRepository is managing the light
// reveal scrim.
- if (!lightRevealMigration()) {
+ if (!ambientAod()) {
if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
circleReveal?.let {
lightRevealScrim.revealAmount = 0f
@@ -213,7 +213,7 @@ constructor(
}
override fun onKeyguardFadingAwayChanged() {
- if (lightRevealMigration()) {
+ if (ambientAod()) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
index 9db7b50905f8..1301fb87069e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
@@ -17,9 +17,9 @@
package com.android.systemui.common.domain.interactor
import android.util.Log
+import com.android.app.displaylib.PerDisplayRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
-import com.android.systemui.display.data.repository.PerDisplayRepository
import com.android.systemui.model.StateChange
import com.android.systemui.model.SysUiState
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
index 2adaec21867f..6792f3188986 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -19,6 +19,7 @@ package com.android.systemui.common.shared.model
import android.annotation.DrawableRes
import android.graphics.drawable.Drawable
import androidx.compose.runtime.Stable
+import com.android.systemui.common.shared.model.Icon.Loaded
/**
* Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference]
@@ -33,8 +34,37 @@ sealed class Icon {
constructor(
val drawable: Drawable,
override val contentDescription: ContentDescription?,
+ /**
+ * Serves as an id to compare two instances. When provided this is used alongside
+ * [contentDescription] to determine equality. This is useful when comparing icons
+ * representing the same UI, but with different [drawable] instances.
+ */
@DrawableRes val res: Int? = null,
- ) : Icon()
+ ) : Icon() {
+
+ override fun equals(other: Any?): Boolean {
+ val that = other as? Loaded ?: return false
+
+ if (this.res != null && that.res != null) {
+ return this.res == that.res && this.contentDescription == that.contentDescription
+ }
+
+ return this.res == that.res &&
+ this.drawable == that.drawable &&
+ this.contentDescription == that.contentDescription
+ }
+
+ override fun hashCode(): Int {
+ var result = contentDescription?.hashCode() ?: 0
+ result =
+ if (res != null) {
+ 31 * result + res.hashCode()
+ } else {
+ 31 * result + drawable.hashCode()
+ }
+ return result
+ }
+ }
data class Resource(
@DrawableRes val res: Int,
@@ -49,4 +79,4 @@ sealed class Icon {
fun Drawable.asIcon(
contentDescription: ContentDescription? = null,
@DrawableRes res: Int? = null,
-): Icon.Loaded = Icon.Loaded(this, contentDescription, res)
+): Loaded = Loaded(this, contentDescription, res)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 756edb3d048d..5a4b0b0e2d24 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -229,7 +229,7 @@ constructor(
MutableStateFlow(false)
}
- val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx / 2.0f
+ val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx
init {
// Initialize our media host for the UMO. This only needs to happen once and must be done
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
index 39708a743c23..3520439fda88 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
@@ -16,9 +16,9 @@
package com.android.systemui.dagger
-import com.android.systemui.display.data.repository.DefaultDisplayOnlyInstanceRepositoryImpl
-import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl
-import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.app.displaylib.DefaultDisplayOnlyInstanceRepositoryImpl
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
+import com.android.app.displaylib.PerDisplayRepository
import com.android.systemui.model.SysUIStateInstanceProvider
import com.android.systemui.model.SysUiState
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index c201fbf0051b..edee64e50b53 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -65,7 +65,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.deviceentry.DeviceEntryModule;
import com.android.systemui.display.DisplayModule;
-import com.android.systemui.display.data.repository.PerDisplayRepository;
+import com.android.app.displaylib.PerDisplayRepository;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
import com.android.systemui.flags.FeatureFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index f3316958f01d..908d0aafb2b9 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -18,7 +18,9 @@ package com.android.systemui.display
import android.hardware.display.DisplayManager
import android.os.Handler
+import com.android.app.displaylib.DisplayLibBackground
import com.android.app.displaylib.DisplayLibComponent
+import com.android.app.displaylib.PerDisplayRepository
import com.android.app.displaylib.createDisplayLibComponent
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -34,7 +36,6 @@ import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepos
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper
-import com.android.systemui.display.data.repository.PerDisplayRepository
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule
@@ -85,6 +86,10 @@ interface DisplayModule {
@Binds
fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback
+ @Binds
+ @DisplayLibBackground
+ fun bindDisplayLibBackground(@Background bgScope: CoroutineScope): CoroutineScope
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
index a56710ee3772..86c9d84c27b1 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
@@ -16,6 +16,9 @@
package com.android.systemui.display.data.repository
+import com.android.app.displaylib.PerDisplayRepository
+
+// TODO b/401305290 - move to displaylib
class FakePerDisplayRepository<T> : PerDisplayRepository<T> {
private val instances = mutableMapOf<Int, T>()
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
index 212d55612935..efbae5d04caf 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
@@ -16,6 +16,7 @@
package com.android.systemui.display.data.repository
+import com.android.app.displaylib.PerDisplayRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.DumpableFromToString
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
deleted file mode 100644
index 7e00c60dc43a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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.display.data.repository
-
-import android.util.Log
-import android.view.Display
-import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.app.tracing.traceSection
-import com.android.systemui.dagger.qualifiers.Background
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import java.util.concurrent.ConcurrentHashMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collectLatest
-
-/**
- * Used to create instances of type `T` for a specific display.
- *
- * This is useful for resources or objects that need to be managed independently for each connected
- * display (e.g., UI state, rendering contexts, or display-specific configurations).
- *
- * Note that in most cases this can be implemented by a simple `@AssistedFactory` with `displayId`
- * parameter
- *
- * ```kotlin
- * class SomeType @AssistedInject constructor(@Assisted displayId: Int,..)
- * @AssistedFactory
- * interface Factory {
- * fun create(displayId: Int): SomeType
- * }
- * }
- * ```
- *
- * Then it can be used to create a [PerDisplayRepository] as follows:
- * ```kotlin
- * // Injected:
- * val repositoryFactory: PerDisplayRepositoryImpl.Factory
- * val instanceFactory: PerDisplayRepositoryImpl.Factory
- * // repository creation:
- * repositoryFactory.create(instanceFactory::create)
- * ```
- *
- * @see PerDisplayRepository For how to retrieve and manage instances created by this factory.
- */
-fun interface PerDisplayInstanceProvider<T> {
- /** Creates an instance for a display. */
- fun createInstance(displayId: Int): T?
-}
-
-/**
- * Extends [PerDisplayInstanceProvider], adding support for destroying the instance.
- *
- * This is useful for releasing resources associated with a display when it is disconnected or when
- * the per-display instance is no longer needed.
- */
-interface PerDisplayInstanceProviderWithTeardown<T> : PerDisplayInstanceProvider<T> {
- /** Destroys a previously created instance of `T` forever. */
- fun destroyInstance(instance: T)
-}
-
-/**
- * Provides access to per-display instances of type `T`.
- *
- * Acts as a repository, managing the caching and retrieval of instances created by a
- * [PerDisplayInstanceProvider]. It ensures that only one instance of `T` exists per display ID.
- */
-interface PerDisplayRepository<T> {
- /** Gets the cached instance or create a new one for a given display. */
- operator fun get(displayId: Int): T?
-
- /** Debug name for this repository, mainly for tracing and logging. */
- val debugName: String
-
- /**
- * Callback to run when a given repository is initialized.
- *
- * This allows the caller to perform custom logic when the repository is ready to be used, e.g.
- * register to dumpManager.
- *
- * Note that the instance is *leaked* outside of this class, so it should only be done when
- * repository is meant to live as long as the caller. In systemUI this is ok because the
- * repository lives as long as the process itself.
- */
- interface InitCallback {
- fun onInit(debugName: String, instance: Any)
- }
-}
-
-/**
- * Default implementation of [PerDisplayRepository].
- *
- * This class manages a cache of per-display instances of type `T`, creating them using a provided
- * [PerDisplayInstanceProvider] and optionally tearing them down using a
- * [PerDisplayInstanceProviderWithTeardown] when displays are disconnected.
- *
- * It listens to the [DisplayRepository] to detect when displays are added or removed, and
- * automatically manages the lifecycle of the per-display instances.
- *
- * Note that this is a [PerDisplayStoreImpl] 2.0 that doesn't require [CoreStartable] bindings,
- * providing all args in the constructor.
- */
-class PerDisplayInstanceRepositoryImpl<T>
-@AssistedInject
-constructor(
- @Assisted override val debugName: String,
- @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>,
- @Background private val backgroundApplicationScope: CoroutineScope,
- private val displayRepository: DisplayRepository,
- private val initCallback: PerDisplayRepository.InitCallback,
-) : PerDisplayRepository<T> {
-
- private val perDisplayInstances = ConcurrentHashMap<Int, T?>()
-
- init {
- backgroundApplicationScope.launch("$debugName#start") { start() }
- }
-
- private suspend fun start() {
- initCallback.onInit(debugName, this)
- displayRepository.displayIds.collectLatest { displayIds ->
- val toRemove = perDisplayInstances.keys - displayIds
- toRemove.forEach { displayId ->
- Log.d(TAG, "<$debugName> destroying instance for displayId=$displayId.")
- perDisplayInstances.remove(displayId)?.let { instance ->
- (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance(
- instance
- )
- }
- }
- }
- }
-
- override fun get(displayId: Int): T? {
- if (displayRepository.getDisplay(displayId) == null) {
- Log.e(TAG, "<$debugName: Display with id $displayId doesn't exist.")
- return null
- }
-
- // If it doesn't exist, create it and put it in the map.
- return perDisplayInstances.computeIfAbsent(displayId) { key ->
- Log.d(TAG, "<$debugName> creating instance for displayId=$key, as it wasn't available.")
- val instance =
- traceSection({ "creating instance of $debugName for displayId=$key" }) {
- instanceProvider.createInstance(key)
- }
- if (instance == null) {
- Log.e(
- TAG,
- "<$debugName> returning null because createInstance($key) returned null.",
- )
- }
- instance
- }
- }
-
- @AssistedFactory
- interface Factory<T> {
- fun create(
- debugName: String,
- instanceProvider: PerDisplayInstanceProvider<T>,
- ): PerDisplayInstanceRepositoryImpl<T>
- }
-
- companion object {
- private const val TAG = "PerDisplayInstanceRepo"
- }
-
- override fun toString(): String {
- return "PerDisplayInstanceRepositoryImpl(" +
- "debugName='$debugName', instances=$perDisplayInstances)"
- }
-}
-
-/**
- * Provides an instance of a given class **only** for the default display, even if asked for another
- * display.
- *
- * This is useful in case of **flag refactors**: it can be provided instead of an instance of
- * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off.
- *
- * Note that this still requires all instances to be provided by a [PerDisplayInstanceProvider]. If
- * you want to provide an existing instance instead for the default display, either implement it in
- * a custom [PerDisplayInstanceProvider] (e.g. inject it in the constructor and return it if the
- * displayId is zero), or use [SingleInstanceRepositoryImpl].
- */
-class DefaultDisplayOnlyInstanceRepositoryImpl<T>(
- override val debugName: String,
- private val instanceProvider: PerDisplayInstanceProvider<T>,
-) : PerDisplayRepository<T> {
- private val lazyDefaultDisplayInstance by lazy {
- instanceProvider.createInstance(Display.DEFAULT_DISPLAY)
- }
-
- override fun get(displayId: Int): T? = lazyDefaultDisplayInstance
-}
-
-/**
- * Always returns [instance] for any display.
- *
- * This can be used to provide a single instance based on a flag value during a refactor. Similar to
- * [DefaultDisplayOnlyInstanceRepositoryImpl], but also avoids creating the
- * [PerDisplayInstanceProvider]. This is useful when you want to provide an existing instance only,
- * without even instantiating a [PerDisplayInstanceProvider].
- */
-class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) :
- PerDisplayRepository<T> {
- override fun get(displayId: Int): T? = instance
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index c61530c3dbcc..74cf7e4f7359 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,7 +20,6 @@ package com.android.systemui.keyguard
import android.content.Context
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
@@ -46,6 +45,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.VibratorHelper
@@ -105,7 +105,7 @@ constructor(
bindJankViewModel()
initializeViews()
- if (lightRevealMigration()) {
+ if (ambientAod()) {
LightRevealScrimViewBinder.bind(
lightRevealScrim,
lightRevealScrimViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 0b116ded42da..438dff9cb476 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -139,7 +139,7 @@ constructor(
fun setWallpaperSupportsAmbientMode(supportsAmbientMode: Boolean) {
repository.maxAlpha.value =
if (supportsAmbientMode) {
- 0.7f
+ 0.54f
} else {
1f
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt
index 45f8f10595e4..3cf05062325d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.util.MathUtils
-import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.communal.ui.compose.TransitionDuration
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.dagger.GlanceableHubBlurComponent
@@ -28,6 +27,7 @@ import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shared.Flags.ambientAod
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -56,14 +56,14 @@ constructor(
return transitionAnimation.sharedFlow(
duration = 250.milliseconds,
startTime =
- if (lightRevealMigration()) {
+ if (ambientAod()) {
100.milliseconds // Wait for the light reveal to "hit" the LS elements.
} else {
0.milliseconds
},
onStart = {
currentAlpha =
- if (lightRevealMigration()) {
+ if (ambientAod()) {
viewState.alpha()
} else {
0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index d981eeb0989b..ba6bda8a2a04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.util.MathUtils
-import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -25,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.shared.Flags.ambientAod
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -52,13 +52,13 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra
return transitionAnimation.sharedFlow(
duration = 250.milliseconds,
startTime =
- if (lightRevealMigration()) {
+ if (ambientAod()) {
100.milliseconds // Wait for the light reveal to "hit" the LS elements.
} else {
0.milliseconds
},
onStart = {
- if (lightRevealMigration()) {
+ if (ambientAod()) {
currentAlpha = viewState.alpha()
} else {
currentAlpha = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
index b15cacf077a4..2eb5bf9328e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.util.MathUtils
-import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -25,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.shared.Flags.ambientAod
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -56,7 +56,7 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra
duration = 250.milliseconds,
startTime = 0.milliseconds,
onStart = {
- if (lightRevealMigration()) {
+ if (ambientAod()) {
currentAlpha = viewState.alpha()
} else {
currentAlpha = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
index c6e4db7af2d9..324a3efc2989 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
@@ -150,6 +150,13 @@ private class OptionsAdapter(context: Context, private val options: List<ScreenS
titleTextView.isEnabled = true
} else {
errorTextView.visibility = View.VISIBLE
+ if (com.android.systemui.Flags.mediaProjectionGreyErrorText()) {
+ errorTextView.isEnabled = false
+ errorTextView.setTextColor(context.getColorStateList(R.color.menu_item_text))
+ errorTextView.setText(
+ R.string.media_projection_entry_app_permission_dialog_single_app_not_supported
+ )
+ }
titleTextView.isEnabled = false
}
return view
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
index 71cb74543485..68cd80798c4b 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
@@ -17,9 +17,9 @@ package com.android.systemui.model
import android.util.Log
import android.view.Display
+import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown
import com.android.systemui.dump.DumpManager
import com.android.systemui.model.SysUiState.SysUiStateCallback
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index 58ddbf60e8fb..39482bea0111 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -24,10 +24,10 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import com.android.app.displaylib.PerDisplayRepository;
import com.android.app.viewcapture.ViewCapture;
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.dagger.qualifiers.DisplayId;
-import com.android.systemui.display.data.repository.PerDisplayRepository;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
import com.android.systemui.navigationbar.views.NavigationBarFrame;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index d20b360756d7..728652e6e5c4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -82,6 +82,7 @@ fun ContentScope.QuickQuickSettings(
viewModel.tileHapticsViewModelFactoryProvider,
// There should be no QuickQuickSettings when the details view is enabled.
detailsViewModel = null,
+ isVisible = listening,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
index d40ecc9565ae..1176095fbb1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.panels.ui.compose
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -23,11 +24,13 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -63,6 +66,7 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode
val title = tileDetailedViewModel.title
val subTitle = tileDetailedViewModel.subTitle
+ val colors = MaterialTheme.colorScheme
Column(
modifier =
@@ -70,20 +74,33 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode
.fillMaxWidth()
// The height of the details view is TBD.
.fillMaxHeight()
+ .background(color = colors.onPrimary)
) {
CompositionLocalProvider(
value = LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant
) {
Row(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ start = TileDetailsDefaults.TitleRowStart,
+ top = TileDetailsDefaults.TitleRowTop,
+ end = TileDetailsDefaults.TitleRowEnd,
+ bottom = TileDetailsDefaults.TitleRowBottom
+ ),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(
onClick = { detailsViewModel.closeDetailedView() },
+ colors = IconButtonDefaults.iconButtonColors(
+ contentColor = colors.onSurface
+ ),
modifier =
- Modifier.align(Alignment.CenterVertically)
+ Modifier
+ .align(Alignment.CenterVertically)
.height(TileDetailsDefaults.IconHeight)
+ .width(TileDetailsDefaults.IconWidth)
.padding(start = TileDetailsDefaults.IconPadding),
) {
Icon(
@@ -96,13 +113,19 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode
text = title,
modifier = Modifier.align(Alignment.CenterVertically),
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleLarge,
+ style = MaterialTheme.typography.titleMedium,
+ color = colors.onSurface,
)
IconButton(
onClick = { tileDetailedViewModel.clickOnSettingsButton() },
+ colors = IconButtonDefaults.iconButtonColors(
+ contentColor = colors.onSurface
+ ),
modifier =
- Modifier.align(Alignment.CenterVertically)
+ Modifier
+ .align(Alignment.CenterVertically)
.height(TileDetailsDefaults.IconHeight)
+ .width(TileDetailsDefaults.IconWidth)
.padding(end = TileDetailsDefaults.IconPadding),
) {
Icon(
@@ -116,7 +139,8 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode
text = subTitle,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleSmall,
+ style = MaterialTheme.typography.bodySmall,
+ color = colors.onSurfaceVariant,
)
}
MapTileDetailsContent(tileDetailedViewModel)
@@ -135,6 +159,11 @@ private fun MapTileDetailsContent(tileDetailsViewModel: TileDetailsViewModel) {
}
private object TileDetailsDefaults {
- val IconHeight = 48.dp
+ val IconHeight = 24.dp
+ val IconWidth = 24.dp
val IconPadding = 4.dp
+ val TitleRowStart = 14.dp
+ val TitleRowTop = 22.dp
+ val TitleRowEnd = 20.dp
+ val TitleRowBottom = 8.dp
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 50012abc69d6..699778f3b6f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -39,12 +39,14 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -102,6 +104,7 @@ fun LargeTileContent(
sideDrawable: Drawable?,
colors: TileColors,
squishiness: () -> Float,
+ isVisible: () -> Boolean = { true },
accessibilityUiState: AccessibilityUiState? = null,
iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
toggleClick: (() -> Unit)? = null,
@@ -158,6 +161,7 @@ fun LargeTileContent(
secondaryLabel = secondaryLabel,
colors = colors,
accessibilityUiState = accessibilityUiState,
+ isVisible = isVisible,
)
if (sideDrawable != null) {
@@ -170,12 +174,14 @@ fun LargeTileContent(
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun LargeTileLabels(
label: String,
secondaryLabel: String?,
colors: TileColors,
modifier: Modifier = Modifier,
+ isVisible: () -> Boolean = { true },
accessibilityUiState: AccessibilityUiState? = null,
) {
val animatedLabelColor by animateColorAsState(colors.label, label = "QSTileLabelColor")
@@ -184,14 +190,16 @@ fun LargeTileLabels(
Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) {
TileLabel(
text = label,
- style = MaterialTheme.typography.labelLarge,
+ style = MaterialTheme.typography.titleSmallEmphasized,
color = { animatedLabelColor },
+ isVisible = isVisible,
)
if (!TextUtils.isEmpty(secondaryLabel)) {
TileLabel(
secondaryLabel ?: "",
color = { animatedSecondaryLabelColor },
- style = MaterialTheme.typography.bodyMedium,
+ style = MaterialTheme.typography.labelMedium,
+ isVisible = isVisible,
modifier =
Modifier.thenIf(
accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") ==
@@ -277,36 +285,50 @@ private fun TileLabel(
color: ColorProducer,
style: TextStyle,
modifier: Modifier = Modifier,
+ isVisible: () -> Boolean = { true },
) {
+ var textSize by remember { mutableIntStateOf(0) }
+
BasicText(
text = text,
color = color,
style = style,
maxLines = 1,
+ onTextLayout = { textSize = it.size.width },
modifier =
modifier
.fillMaxWidth()
- .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
+ .graphicsLayer {
+ if (textSize > size.width) {
+ compositingStrategy = CompositingStrategy.Offscreen
+ }
+ }
.drawWithContent {
drawContent()
- // Draw a blur over the end of the text
- val edgeWidthPx = TileLabelBlurWidth.toPx()
- drawRect(
- topLeft = Offset(size.width - edgeWidthPx, 0f),
- size = Size(edgeWidthPx, size.height),
- brush =
- Brush.horizontalGradient(
- colors = listOf(Color.Transparent, Color.Black),
- startX = size.width,
- endX = size.width - edgeWidthPx,
- ),
- blendMode = BlendMode.DstIn,
- )
+ if (textSize > size.width) {
+ // Draw a blur over the end of the text
+ val edgeWidthPx = TileLabelBlurWidth.toPx()
+ drawRect(
+ topLeft = Offset(size.width - edgeWidthPx, 0f),
+ size = Size(edgeWidthPx, size.height),
+ brush =
+ Brush.horizontalGradient(
+ colors = listOf(Color.Transparent, Color.Black),
+ startX = size.width,
+ endX = size.width - edgeWidthPx,
+ ),
+ blendMode = BlendMode.DstIn,
+ )
+ }
}
- .basicMarquee(
- iterations = TILE_MARQUEE_ITERATIONS,
- initialDelayMillis = TILE_INITIAL_DELAY_MILLIS,
- ),
+ .thenIf(isVisible()) {
+ // Only apply the marquee when the label is visible, which is needed for the
+ // always composed QS
+ Modifier.basicMarquee(
+ iterations = TILE_MARQUEE_ITERATIONS,
+ initialDelayMillis = TILE_INITIAL_DELAY_MILLIS,
+ )
+ },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index ccbd8fdbe00c..46f05d0ac895 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -65,6 +65,7 @@ import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
@@ -109,11 +110,11 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastMap
import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory
import com.android.compose.modifiers.height
@@ -165,7 +166,7 @@ import kotlinx.coroutines.launch
object TileType
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer
@@ -177,7 +178,8 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
),
title = {
Text(
- text = stringResource(id = R.string.qs_edit),
+ text = stringResource(id = R.string.qs_edit_tiles),
+ style = MaterialTheme.typography.titleLargeEmphasized,
modifier = Modifier.padding(start = 24.dp),
)
},
@@ -204,7 +206,10 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
contentColor = MaterialTheme.colorScheme.onPrimary,
),
) {
- Text(stringResource(id = com.android.internal.R.string.reset))
+ Text(
+ text = stringResource(id = com.android.internal.R.string.reset),
+ style = MaterialTheme.typography.labelLarge,
+ )
}
}
},
@@ -212,6 +217,7 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
)
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun DefaultEditTileGrid(
listState: EditTileListState,
@@ -283,7 +289,9 @@ fun DefaultEditTileGrid(
}
}
} else {
- Text(text = stringResource(id = R.string.drag_to_rearrange_tiles))
+ EditGridCenteredText(
+ text = stringResource(id = R.string.drag_to_rearrange_tiles)
+ )
}
}
}
@@ -401,6 +409,11 @@ private fun EditGridHeader(
}
@Composable
+private fun EditGridCenteredText(text: String, modifier: Modifier = Modifier) {
+ Text(text = text, style = MaterialTheme.typography.titleSmall, modifier = modifier)
+}
+
+@Composable
private fun RemoveTileTarget(onClick: () -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -486,6 +499,7 @@ private fun CurrentTilesGrid(
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun AvailableTileGrid(
tiles: List<AvailableTileGridCell>,
@@ -524,7 +538,7 @@ private fun AvailableTileGrid(
) {
Text(
text = category.label.load() ?: "",
- fontSize = 20.sp,
+ style = MaterialTheme.typography.titleMediumEmphasized,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.fillMaxWidth().padding(start = 8.dp, bottom = 16.dp),
)
@@ -737,6 +751,7 @@ private fun TileGridCell(
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun AvailableTileGridCell(
cell: AvailableTileGridCell,
@@ -803,6 +818,7 @@ private fun AvailableTileGridCell(
color = colors.label,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.labelMedium.copy(hyphens = Hyphens.Auto),
modifier = Modifier.align(Alignment.TopCenter),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 27e609232a4c..984343a45797 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -119,6 +119,7 @@ constructor(
isLastInRow = isLastInColumn,
),
detailsViewModel = detailsViewModel,
+ isVisible = listening,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 6bafd432669a..e24718285312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -139,6 +139,7 @@ fun Tile(
bounceableInfo: BounceableInfo,
tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
modifier: Modifier = Modifier,
+ isVisible: () -> Boolean = { true },
detailsViewModel: DetailsViewModel?,
) {
trace(tile.traceName) {
@@ -249,6 +250,7 @@ fun Tile(
onLongClick = longClick,
accessibilityUiState = uiState.accessibilityUiState,
squishiness = squishiness,
+ isVisible = isVisible,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
index d2639654d206..5b9717535ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
@@ -78,6 +78,7 @@ import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
+import com.android.app.displaylib.PerDisplayRepository;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -90,7 +91,6 @@ import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.display.data.repository.DisplayRepository;
-import com.android.systemui.display.data.repository.PerDisplayRepository;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
@@ -126,8 +126,6 @@ import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInterface;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -139,6 +137,8 @@ import java.util.function.Supplier;
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
+
/**
* Class to send information from SysUI to Launcher with a binder.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 74b3f302cdc8..b94e7b249233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -36,8 +37,10 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.Expandable
+import com.android.compose.modifiers.thenIf
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
@@ -79,6 +82,17 @@ fun OngoingActivityChip(
}
is OngoingActivityChipModel.ClickBehavior.None -> null
}
+ val isClickable = onClick != null
+
+ val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
+ val minWidth =
+ if (isClickable) {
+ dimensionResource(id = R.dimen.min_clickable_item_size)
+ } else if (model.icon != null) {
+ dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding
+ } else {
+ dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
+ }
Expandable(
color = Color(model.colors.background(LocalContext.current).defaultColor),
@@ -92,6 +106,15 @@ fun OngoingActivityChip(
this.contentDescription = contentDescription
}
}
+ .thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ if (constraints.maxWidth >= minWidth.roundToPx()) {
+ placeable.place(0, 0)
+ }
+ }
+ }
.graphicsLayer(
alpha =
if (model.transitionManager?.hideChipForTransition == true) {
@@ -103,9 +126,12 @@ fun OngoingActivityChip(
borderStroke = borderStroke,
onClick = onClick,
useModifierBasedImplementation = StatusBarChipsReturnAnimations.isEnabled,
+ // Some chips like the 3-2-1 countdown chip should be very small, smaller than a
+ // reasonable minimum size.
+ defaultMinSize = false,
transitionControllerFactory = model.transitionManager?.controllerFactory,
) {
- ChipBody(model, iconViewStore, isClickable = onClick != null)
+ ChipBody(model, iconViewStore, isClickable = isClickable, minWidth = minWidth)
}
}
@@ -114,36 +140,22 @@ private fun ChipBody(
model: OngoingActivityChipModel.Active,
iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
isClickable: Boolean,
+ minWidth: Dp,
modifier: Modifier = Modifier,
) {
val hasEmbeddedIcon =
model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView ||
model.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
- val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
- val minWidth =
- if (isClickable) {
- dimensionResource(id = R.dimen.min_clickable_item_size)
- } else if (model.icon != null) {
- dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding
- } else {
- dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
- }
-
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier =
modifier
.fillMaxHeight()
- .layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
- layout(placeable.width, placeable.height) {
- if (constraints.maxWidth >= minWidth.roundToPx()) {
- placeable.place(0, 0)
- }
- }
- }
+ // Set the minWidth here as well as on the Expandable so that the content within
+ // this row is still centered correctly horizontally
+ .thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
.padding(
horizontal =
if (hasEmbeddedIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt
index 3168a22c56ad..cb1002a83179 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt
@@ -17,14 +17,14 @@
package com.android.systemui.statusbar.data.repository
import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.app.displaylib.PerDisplayInstanceProvider
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
+import com.android.app.displaylib.PerDisplayRepository
+import com.android.app.displaylib.SingleInstanceRepositoryImpl
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.ConfigurationStateImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
-import com.android.systemui.display.data.repository.PerDisplayInstanceProvider
-import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl
-import com.android.systemui.display.data.repository.PerDisplayRepository
-import com.android.systemui.display.data.repository.SingleInstanceRepositoryImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import dagger.Lazy
import dagger.Module
@@ -39,7 +39,6 @@ constructor(
private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore,
private val factory: ConfigurationStateImpl.Factory,
) : PerDisplayInstanceProvider<ConfigurationState> {
-
override fun createInstance(displayId: Int): ConfigurationState? {
val displayWindowProperties =
displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index fcdcc3f698de..fed9417edd88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -23,6 +23,7 @@ import android.view.View;
import com.android.systemui.DejankUtils;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
@@ -44,13 +45,20 @@ public final class NotificationClicker implements View.OnClickListener {
private final Optional<Bubbles> mBubblesOptional;
private final NotificationActivityStarter mNotificationActivityStarter;
- private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener =
- new ExpandableNotificationRow.OnDragSuccessListener() {
- @Override
- public void onDragSuccess(NotificationEntry entry) {
- mNotificationActivityStarter.onDragSuccess(entry);
- }
- };
+ private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener
+ = new ExpandableNotificationRow.OnDragSuccessListener() {
+ @Override
+ public void onDragSuccess(NotificationEntry entry) {
+ NotificationBundleUi.assertInLegacyMode();
+ mNotificationActivityStarter.onDragSuccess(entry);
+ }
+
+ @Override
+ public void onDragSuccess(EntryAdapter entryAdapter) {
+ NotificationBundleUi.isUnexpectedlyInLegacyMode();
+ entryAdapter.onDragSuccess();
+ }
+ };
private NotificationClicker(
NotificationClickerLogger logger,
@@ -73,7 +81,6 @@ public final class NotificationClicker implements View.OnClickListener {
mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- final NotificationEntry entry = row.getEntry();
mLogger.logOnClick(row.getLoggingKey());
// Check if the notification is displaying the menu, if so slide notification back
@@ -101,16 +108,16 @@ public final class NotificationClicker implements View.OnClickListener {
DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
if (NotificationBundleUi.isEnabled()) {
- if (!row.getEntryAdapter().isBubbleCapable() && mBubblesOptional.isPresent()) {
+ if (!row.getEntryAdapter().isBubble() && mBubblesOptional.isPresent()) {
mBubblesOptional.get().collapseStack();
}
+ row.getEntryAdapter().onEntryClicked(row);
} else {
if (!row.getEntryLegacy().isBubble() && mBubblesOptional.isPresent()) {
mBubblesOptional.get().collapseStack();
}
+ mNotificationActivityStarter.onNotificationClicked(row.getEntryLegacy(), row);
}
-
- mNotificationActivityStarter.onNotificationClicked(entry, row);
}
private boolean isMenuVisible(ExpandableNotificationRow row) {
@@ -121,9 +128,12 @@ public final class NotificationClicker implements View.OnClickListener {
* Attaches the click listener to the row if appropriate.
*/
public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+ boolean isBubble = NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().isBubble()
+ : row.getEntryLegacy().isBubble();
Notification notification = sbn.getNotification();
if (notification.contentIntent != null || notification.fullScreenIntent != null
- || row.getEntry().isBubble()) {
+ || isBubble) {
if (NotificationBundleUi.isEnabled()) {
row.setBubbleClickListener(
v -> row.getEntryAdapter().onNotificationBubbleIconClicked());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
index 1fc8efdcf24a..be17ae56c315 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
@@ -22,6 +22,7 @@ import android.os.Build
import android.service.notification.StatusBarNotification
import android.util.Log
import com.android.systemui.statusbar.notification.icon.IconPack
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import kotlinx.coroutines.flow.StateFlow
@@ -97,7 +98,7 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter {
return false
}
- override fun isBubbleCapable(): Boolean {
+ override fun isBubble(): Boolean {
return false
}
@@ -113,6 +114,10 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter {
return false
}
+ override fun getPeopleNotificationType(): Int {
+ return TYPE_NON_PERSON
+ }
+
override fun isPromotedOngoing(): Boolean {
return false
}
@@ -121,6 +126,11 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter {
return false
}
+ override fun onDragSuccess() {
+ // do nothing. these should not be draggable
+ Log.wtf(TAG, "onDragSuccess() called")
+ }
+
override fun onNotificationBubbleIconClicked() {
// do nothing. these cannot be a bubble
Log.wtf(TAG, "onNotificationBubbleIconClicked() called")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 47ffe1030eff..3757ebfb9986 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.statusbar.notification.icon.IconPack;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import kotlinx.coroutines.flow.StateFlow;
@@ -124,7 +125,7 @@ public interface EntryAdapter {
boolean canDragAndDrop();
- boolean isBubbleCapable();
+ boolean isBubble();
@Nullable String getStyle();
@@ -132,6 +133,8 @@ public interface EntryAdapter {
boolean isAmbient();
+ @PeopleNotificationIdentifier.Companion.PeopleNotificationType int getPeopleNotificationType();
+
/**
* Returns whether this row represents promoted ongoing notification.
*/
@@ -141,6 +144,8 @@ public interface EntryAdapter {
return false;
}
+ void onDragSuccess();
+
/**
* Process a click on a notification bubble icon
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
index f78a5ddd3982..a23c5a3ea9f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
@@ -121,7 +121,7 @@ class NotificationEntryAdapter(
return false
}
- override fun isBubbleCapable(): Boolean {
+ override fun isBubble(): Boolean {
return entry.isBubble
}
@@ -137,6 +137,10 @@ class NotificationEntryAdapter(
return entry.ranking.isAmbient
}
+ override fun getPeopleNotificationType(): Int {
+ return peopleNotificationIdentifier.getPeopleNotificationType(entry)
+ }
+
override fun isPromotedOngoing(): Boolean {
return entry.isPromotedOngoing
}
@@ -145,6 +149,10 @@ class NotificationEntryAdapter(
return entry.sbn.notification.fullScreenIntent != null
}
+ override fun onDragSuccess() {
+ notificationActivityStarter.onDragSuccess(entry)
+ }
+
override fun onNotificationBubbleIconClicked() {
notificationActivityStarter.onNotificationBubbleIconClicked(entry)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
index 147a5afea306..619d48f8ba81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
@@ -18,9 +18,9 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder
import android.view.Display
import androidx.lifecycle.lifecycleScope
+import com.android.app.displaylib.PerDisplayRepository
import com.android.app.tracing.traceSection
import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.display.data.repository.PerDisplayRepository
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index d35c3b617246..7e19ff115a92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -21,6 +21,7 @@ import android.app.Notification.BigPictureStyle
import android.app.Notification.BigTextStyle
import android.app.Notification.CallStyle
import android.app.Notification.EXTRA_BIG_TEXT
+import android.app.Notification.EXTRA_CALL_PERSON
import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN
import android.app.Notification.EXTRA_PROGRESS
import android.app.Notification.EXTRA_PROGRESS_INDETERMINATE
@@ -33,6 +34,7 @@ import android.app.Notification.EXTRA_VERIFICATION_ICON
import android.app.Notification.EXTRA_VERIFICATION_TEXT
import android.app.Notification.InboxStyle
import android.app.Notification.ProgressStyle
+import android.app.Person
import android.content.Context
import android.graphics.drawable.Icon
import com.android.systemui.Flags
@@ -108,12 +110,12 @@ constructor(
contentBuilder.shortCriticalText = notification.shortCriticalText()
contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
contentBuilder.profileBadgeResId = null // TODO
- contentBuilder.title = notification.resolveTitle(recoveredBuilder.style)
- contentBuilder.text = notification.resolveText(recoveredBuilder.style)
+ contentBuilder.title = notification.title(recoveredBuilder.style)
+ contentBuilder.text = notification.text(recoveredBuilder.style)
contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider)
contentBuilder.oldProgress = notification.oldProgress()
- val colorsFromNotif = recoveredBuilder.getColors(/* header= */ false)
+ val colorsFromNotif = recoveredBuilder.getColors(/* isHeader= */ false)
contentBuilder.colors =
PromotedNotificationContentModel.Colors(
backgroundColor = colorsFromNotif.backgroundColor,
@@ -132,20 +134,16 @@ constructor(
private fun Notification.bigTitle(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE_BIG)
- private fun Notification.Style.bigTitleOverridesTitle(): Boolean {
- return when (this) {
+ private fun Notification.callPerson(): Person? =
+ extras?.getParcelable(EXTRA_CALL_PERSON, Person::class.java)
+
+ private fun Notification.title(style: Notification.Style?): CharSequence? {
+ return when (style) {
is BigTextStyle,
is BigPictureStyle,
- is InboxStyle -> true
- else -> false
- }
- }
-
- private fun Notification.resolveTitle(style: Notification.Style?): CharSequence? {
- return if (style?.bigTitleOverridesTitle() == true) {
- bigTitle()
- } else {
- null
+ is InboxStyle -> bigTitle()
+ is CallStyle -> callPerson()?.name
+ else -> null
} ?: title()
}
@@ -153,13 +151,10 @@ constructor(
private fun Notification.bigText(): CharSequence? = extras?.getCharSequence(EXTRA_BIG_TEXT)
- private fun Notification.Style.bigTextOverridesText(): Boolean = this is BigTextStyle
-
- private fun Notification.resolveText(style: Notification.Style?): CharSequence? {
- return if (style?.bigTextOverridesText() == true) {
- bigText()
- } else {
- null
+ private fun Notification.text(style: Notification.Style?): CharSequence? {
+ return when (style) {
+ is BigTextStyle -> bigText()
+ else -> null
} ?: text()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
index 468934791525..5f9678a06b59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
@@ -63,7 +63,7 @@ constructor(@PromotedNotificationLog private val buffer: LogBuffer) {
INFO,
{
str1 = entry.logKey
- str2 = content.toString()
+ str2 = content.toRedactedString()
},
{ "extraction succeeded: $str2 for $str1" },
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index 0c2859fa1766..57b07204fc6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -25,6 +25,8 @@ import androidx.annotation.ColorInt
import com.android.internal.widget.NotificationProgressModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.row.ImageResult
+import com.android.systemui.statusbar.notification.row.LazyImage
import com.android.systemui.statusbar.notification.row.shared.ImageModel
/**
@@ -152,6 +154,54 @@ data class PromotedNotificationContentModel(
Ineligible,
}
+ fun toRedactedString(): String {
+ return ("PromotedNotificationContentModel(" +
+ "identity=$identity, " +
+ "wasPromotedAutomatically=$wasPromotedAutomatically, " +
+ "smallIcon=${smallIcon?.toRedactedString()}, " +
+ "appName=$appName, " +
+ "subText=${subText?.toRedactedString()}, " +
+ "shortCriticalText=$shortCriticalText, " +
+ "time=$time, " +
+ "lastAudiblyAlertedMs=$lastAudiblyAlertedMs, " +
+ "profileBadgeResId=$profileBadgeResId, " +
+ "title=${title?.toRedactedString()}, " +
+ "text=${text?.toRedactedString()}, " +
+ "skeletonLargeIcon=${skeletonLargeIcon?.toRedactedString()}, " +
+ "oldProgress=$oldProgress, " +
+ "colors=$colors, " +
+ "style=$style, " +
+ "personIcon=${personIcon?.toRedactedString()}, " +
+ "personName=${personName?.toRedactedString()}, " +
+ "verificationIcon=$verificationIcon, " +
+ "verificationText=$verificationText, " +
+ "newProgress=$newProgress)")
+ }
+
+ private fun CharSequence.toRedactedString(): String = "[$length]"
+
+ private fun ImageModel.toRedactedString(): String {
+ return when (this) {
+ is LazyImage -> this.toRedactedString()
+ else -> this.toString()
+ }
+ }
+
+ private fun LazyImage.toRedactedString(): String {
+ return ("LazyImage(" +
+ "icon=[${icon.javaClass.simpleName}], " +
+ "sizeClass=$sizeClass, " +
+ "transform=$transform, " +
+ "result=${result?.toRedactedString()})")
+ }
+
+ private fun ImageResult.toRedactedString(): String {
+ return when (this) {
+ is ImageResult.Empty -> this.toString()
+ is ImageResult.Image -> "Image(drawable=[${drawable.javaClass.simpleName}])"
+ }
+ }
+
companion object {
@JvmStatic
fun featureFlagEnabled(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 1a76d5d9d9e4..2a3b266c8d10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -22,9 +22,9 @@ import static android.service.notification.NotificationListenerService.REASON_CA
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+import static com.android.systemui.Flags.notificationRowAccessibilityExpanded;
import static com.android.systemui.Flags.notificationRowTransparency;
import static com.android.systemui.Flags.notificationsPinnedHunInShade;
-import static com.android.systemui.Flags.notificationRowAccessibilityExpanded;
import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
@@ -671,8 +671,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private boolean isConversation() {
- return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntry())
- != PeopleNotificationIdentifier.TYPE_NON_PERSON;
+ if (NotificationBundleUi.isEnabled()) {
+ return getEntryAdapter().getPeopleNotificationType()
+ != PeopleNotificationIdentifier.TYPE_NON_PERSON;
+ } else {
+ return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntryLegacy())
+ != PeopleNotificationIdentifier.TYPE_NON_PERSON;
+ }
}
public void onNotificationUpdated() {
@@ -2515,7 +2520,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
public void dragAndDropSuccess() {
if (mOnDragSuccessListener != null) {
- mOnDragSuccessListener.onDragSuccess(getEntry());
+ if (NotificationBundleUi.isEnabled()) {
+ mOnDragSuccessListener.onDragSuccess(getEntryAdapter());
+ } else {
+ mOnDragSuccessListener.onDragSuccess(getEntryLegacy());
+ }
}
}
@@ -4002,7 +4011,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
} else if (isChildInGroup()) {
final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
- if (Flags.notificationRowTransparency() && childColor == Color.TRANSPARENT) {
+ if ((Flags.notificationRowTransparency() || notificationsRedesignTemplates())
+ && childColor == Color.TRANSPARENT) {
// If child is not customizing its background color, switch from the parent to
// the child background when the expansion finishes.
mShowNoBackground = !mNotificationParent.mShowNoBackground;
@@ -4416,6 +4426,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* @param entry NotificationEntry that succeed to drop on proper target window.
*/
void onDragSuccess(NotificationEntry entry);
+
+ /**
+ * @param entryAdapter The EntryAdapter that successfully dropped on the proper
+ * target window
+ */
+ void onDragSuccess(EntryAdapter entryAdapter);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
index fe3a856e711e..a9ca6359b570 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
@@ -62,6 +62,7 @@ class BaseBackgroundDrawable(
private val buttonShape = Path()
// Color and style
+ private val outlineStaticColor = context.getColor(R.color.magic_action_button_stroke_color)
private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
val bgColor =
context.getColor(
@@ -70,15 +71,17 @@ class BaseBackgroundDrawable(
color = bgColor
style = Paint.Style.FILL
}
- private val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
- val outlineColor =
- context.getColor(
- com.android.internal.R.color.materialColorOutlineVariant
- )
- color = outlineColor
+ private val outlineGradientPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = outlineStaticColor
+ style = Paint.Style.STROKE
+ strokeWidth = outlineStrokeWidth
+ }
+ private val outlineSolidPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = outlineStaticColor
style = Paint.Style.STROKE
strokeWidth = outlineStrokeWidth
}
+
private val outlineStartColor =
context.getColor(
com.android.internal.R.color.materialColorTertiaryContainer
@@ -91,21 +94,35 @@ class BaseBackgroundDrawable(
context.getColor(
com.android.internal.R.color.materialColorPrimary
)
+
// Animation
private var gradientAnimator: ValueAnimator
private var rotationAngle = 20f // Start rotation at 20 degrees
+ private var fadeAnimator: ValueAnimator? = null
+ private var gradientAlpha = 255 // Fading out gradient
+ private var solidAlpha = 0 // Fading in solid color
init {
gradientAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 5000 // 5 seconds
+ duration = 1500
interpolator = Interpolators.LINEAR
- repeatCount = 1
+ repeatCount = 0
addUpdateListener { animator ->
val animatedValue = animator.animatedValue as Float
rotationAngle = 20f + animatedValue * 360f // Rotate in a spiral
invalidateSelf()
}
- // TODO: Reset the outline color when animation ends.
+ start()
+ }
+ fadeAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 500
+ startDelay = 1000
+ addUpdateListener { animator ->
+ val progress = animator.animatedValue as Float
+ gradientAlpha = ((1 - progress) * 255).toInt() // Fade out gradient
+ solidAlpha = (progress * 255).toInt() // Fade in color
+ invalidateSelf()
+ }
start()
}
}
@@ -120,14 +137,9 @@ class BaseBackgroundDrawable(
// Draw background
canvas.clipPath(buttonShape)
canvas.drawPath(buttonShape, bgPaint)
- // Apply gradient to outline
- canvas.drawPath(buttonShape, outlinePaint)
- updateGradient(boundsF)
- canvas.restore()
- }
- private fun updateGradient(boundsF: RectF) {
- val gradient = LinearGradient(
+ // Set up outline gradient
+ val gradientShader = LinearGradient(
boundsF.left, boundsF.top,
boundsF.right, boundsF.bottom,
intArrayOf(outlineStartColor, outlineMiddleColor, outlineEndColor),
@@ -137,9 +149,17 @@ class BaseBackgroundDrawable(
// Create a rotation matrix for the spiral effect
val matrix = Matrix()
matrix.setRotate(rotationAngle, boundsF.centerX(), boundsF.centerY())
- gradient.setLocalMatrix(matrix)
+ gradientShader.setLocalMatrix(matrix)
+
+ // Apply gradient to outline
+ outlineGradientPaint.shader = gradientShader
+ outlineGradientPaint.alpha = gradientAlpha
+ canvas.drawPath(buttonShape, outlineGradientPaint)
+ // Apply solid color to outline
+ outlineSolidPaint.alpha = solidAlpha
+ canvas.drawPath(buttonShape, outlineSolidPaint)
- outlinePaint.shader = gradient
+ canvas.restore()
}
override fun onBoundsChange(bounds: Rect) {
@@ -149,13 +169,15 @@ class BaseBackgroundDrawable(
override fun setAlpha(alpha: Int) {
bgPaint.alpha = alpha
- outlinePaint.alpha = alpha
+ outlineGradientPaint.alpha = alpha
+ outlineSolidPaint.alpha = alpha
invalidateSelf()
}
override fun setColorFilter(colorFilter: ColorFilter?) {
bgPaint.colorFilter = colorFilter
- outlinePaint.colorFilter = colorFilter
+ outlineGradientPaint.colorFilter = colorFilter
+ outlineSolidPaint.colorFilter = colorFilter
invalidateSelf()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 26d318bea5cc..488aa44ddd3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1604,12 +1604,15 @@ public class NotificationContentView extends FrameLayout implements Notification
}
if (shouldShowBubbleButton(entry)) {
+ boolean isBubble = NotificationBundleUi.isEnabled()
+ ? mContainingNotification.getEntryAdapter().isBubble()
+ : entry.isBubble();
// explicitly resolve drawable resource using SystemUI's theme
- Drawable d = mContext.getDrawable(entry.isBubble()
+ Drawable d = mContext.getDrawable(isBubble
? com.android.wm.shell.R.drawable.bubble_ic_stop_bubble
: com.android.wm.shell.R.drawable.bubble_ic_create_bubble);
- String contentDescription = mContext.getResources().getString(entry.isBubble()
+ String contentDescription = mContext.getResources().getString(isBubble
? R.string.notification_conversation_unbubble
: R.string.notification_conversation_bubble);
@@ -1652,12 +1655,18 @@ public class NotificationContentView extends FrameLayout implements Notification
@VisibleForTesting
boolean shouldShowBubbleButton(NotificationEntry entry) {
- boolean isPersonWithShortcut =
- mPeopleIdentifier.getPeopleNotificationType(entry)
- >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
+ int peopleType = NotificationBundleUi.isEnabled()
+ ? mContainingNotification.getEntryAdapter().getPeopleNotificationType()
+ : mPeopleIdentifier.getPeopleNotificationType(entry);
+ Notification.BubbleMetadata bubbleMetadata = NotificationBundleUi.isEnabled()
+ ? mContainingNotification.getEntryAdapter().getSbn().getNotification()
+ .getBubbleMetadata()
+ : entry.getBubbleMetadata();
+ boolean isPersonWithShortcut = peopleType
+ >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
return mBubblesEnabledForUser
&& isPersonWithShortcut
- && entry.getBubbleMetadata() != null;
+ && bubbleMetadata != null;
}
private void applySnoozeAction(View layout) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index e89a76fd5a69..977936fa34fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.AlphaOptimizedImageView;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
@@ -261,15 +262,19 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mSnoozeItem = createSnoozeItem(mContext);
}
mFeedbackItem = createFeedbackItem(mContext);
- NotificationEntry entry = mParent.getEntry();
- int personNotifType = mPeopleNotificationIdentifier.getPeopleNotificationType(entry);
+ int personNotifType = NotificationBundleUi.isEnabled()
+ ? mParent.getEntryAdapter().getPeopleNotificationType()
+ : mPeopleNotificationIdentifier.getPeopleNotificationType(mParent.getEntryLegacy());
+ StatusBarNotification sbn = NotificationBundleUi.isEnabled()
+ ? mParent.getEntryAdapter().getSbn()
+ : mParent.getEntryLegacy().getSbn();
if (personNotifType == PeopleNotificationIdentifier.TYPE_PERSON) {
mInfoItem = createPartialConversationItem(mContext);
} else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) {
mInfoItem = createConversationItem(mContext);
} else if (android.app.Flags.uiRichOngoing()
&& Flags.permissionHelperUiRichOngoing()
- && entry.getSbn().getNotification().isPromotedOngoing()) {
+ && sbn.getNotification().isPromotedOngoing()) {
mInfoItem = createPromotedItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
index 08c1d71b86c9..03990bf3af84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
@@ -87,9 +87,15 @@ constructor(private val userManager: UserManager, dumpManager: DumpManager) :
// It's not a system app at all.
return false
} else {
- // If there's no launch intent, it's probably a headless app.
- val pm = context.packageManager
- return (pm.getLaunchIntentForPackage(info.packageName) == null)
+ // If there's no launch intent, it's probably a headless app. Check for both
+ // direct-aware and -unaware intents; otherwise this will almost certainly fail
+ // for notifications posted before unlocking.
+ val packageLaunchIntent =
+ context.packageManager.getLaunchIntentForPackage(
+ info.packageName,
+ /* includeDirectBootUnaware= */ true,
+ )
+ return packageLaunchIntent == null
}
} else {
// If for some reason we don't have the app info, we don't know; best assume it's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index a4ee4ad6f6ec..9d9f01b571a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,11 +31,11 @@ import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.Flags.keyboardShortcutHelperRewrite;
-import static com.android.systemui.Flags.lightRevealMigration;
import static com.android.systemui.Flags.relockWithPowerButtonImmediately;
import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
+import static com.android.systemui.shared.Flags.ambientAod;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import android.annotation.Nullable;
@@ -980,7 +980,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void onKeyguardGoingAwayChanged() {
- if (lightRevealMigration()) {
+ if (ambientAod()) {
// This code path is not used if the KeyguardTransitionRepository is managing
// the lightreveal scrim.
return;
@@ -2446,7 +2446,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return;
}
- if (lightRevealMigration()) {
+ if (ambientAod()) {
return;
}
@@ -3103,7 +3103,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void onDozeAmountChanged(float linear, float eased) {
- if (!lightRevealMigration()
+ if (!ambientAod()
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
// If wakeAndUnlocking, this is handled in AuthRippleInteractor
if (!mBiometricUnlockController.isWakeAndUnlock()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 36193bd87ce2..3c144625b685 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -49,6 +49,7 @@ import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.Flags;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.qualifiers.Background;
@@ -475,12 +476,14 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
UserHandle.USER_ALL);
updateUserSwitcher();
onThemeChanged();
- collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer,
- mCoroutineDispatcher);
- collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(),
- mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
- collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
- mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+ if (!Flags.glanceableHubV2()) {
+ collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer,
+ mCoroutineDispatcher);
+ collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(),
+ mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+ collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
+ mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+ }
if (NewStatusBarIcons.isEnabled()) {
ComposeView batteryComposeView = new ComposeView(mContext);
UnifiedBatteryViewBinder.bind(
@@ -645,7 +648,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
&& !mDozing
&& !hideForBypass
&& !mDisableStateTracker.isDisabled()
- && (!mCommunalShowing || mExplicitAlpha != -1)
+ && (Flags.glanceableHubV2() || (!mCommunalShowing || mExplicitAlpha != -1))
? View.VISIBLE : View.INVISIBLE;
updateViewState(newAlpha, newVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 0d43789e95a8..8890db3cd943 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -18,7 +18,6 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.DejankUtils
-import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardViewMediator
@@ -26,6 +25,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
+import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -100,7 +100,7 @@ constructor(
duration = LIGHT_REVEAL_ANIMATION_DURATION
interpolator = Interpolators.LINEAR
addUpdateListener {
- if (lightRevealMigration()) return@addUpdateListener
+ if (ambientAod()) return@addUpdateListener
if (lightRevealScrim.revealEffect !is CircleReveal) {
lightRevealScrim.revealAmount = it.animatedValue as Float
}
@@ -116,7 +116,7 @@ constructor(
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator) {
- if (lightRevealMigration()) return
+ if (ambientAod()) return
if (lightRevealScrim.revealEffect !is CircleReveal) {
lightRevealScrim.revealAmount = 1f
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
index 1a8ca9577bd7..f4afc248d11a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.KairosBuilder
import com.android.systemui.kairos.BuildSpec
import com.android.systemui.kairos.ExperimentalKairosApi
import com.android.systemui.kairos.State
+import com.android.systemui.kairos.combine
import com.android.systemui.kairos.flatMap
import com.android.systemui.kairosBuilder
import com.android.systemui.log.table.TableLogBuffer
@@ -55,9 +56,15 @@ constructor(
@Assisted private val isCarrierMerged: State<Boolean>,
) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+ private var dumpCache: DumpCache? = null
+
init {
onActivated {
logDiffsForTable(isCarrierMerged, tableLogBuffer, columnName = "isCarrierMerged")
+ combine(isCarrierMerged, activeRepo) { isCarrierMerged, activeRepo ->
+ DumpCache(isCarrierMerged, activeRepo)
+ }
+ .observe { dumpCache = it }
}
}
@@ -198,13 +205,6 @@ constructor(
override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode }
- private var dumpCache: DumpCache? = null
-
- private data class DumpCache(
- val isCarrierMerged: Boolean,
- val activeRepo: MobileConnectionRepositoryKairos,
- )
-
fun dump(pw: PrintWriter) {
val cache = dumpCache ?: return
val ipw = IndentingPrintWriter(pw, " ")
@@ -227,6 +227,11 @@ constructor(
ipw.decreaseIndent()
}
+ private data class DumpCache(
+ val isCarrierMerged: Boolean,
+ val activeRepo: MobileConnectionRepositoryKairos,
+ )
+
@AssistedFactory
interface Factory {
fun create(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
index e46815954e64..e6c29214e3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
@@ -131,6 +131,8 @@ constructor(
private val mobileRepoFactory: Lazy<ConnectionRepoFactory>,
) : MobileConnectionsRepositoryKairos, Dumpable, KairosBuilder by kairosBuilder() {
+ private var dumpCache: DumpCache? = null
+
init {
dumpManager.registerNormalDumpable("MobileConnectionsRepositoryKairos", this)
}
@@ -253,6 +255,7 @@ constructor(
.asIncremental()
.mapValues { (subId, sub) -> mobileRepoFactory.get().create(subId) }
.applyLatestSpecForKey()
+ .apply { observe { dumpCache = DumpCache(it) } }
}
private val telephonyManagerState: State<Pair<Int?, Set<Int>>> = buildState {
@@ -479,10 +482,6 @@ constructor(
profileClass = profileClass,
)
- private var dumpCache: DumpCache? = null
-
- private data class DumpCache(val repos: Map<Int, FullMobileConnectionRepositoryKairos>)
-
override fun dump(pw: PrintWriter, args: Array<String>) {
val cache = dumpCache ?: return
val ipw = IndentingPrintWriter(pw, " ")
@@ -494,10 +493,16 @@ constructor(
ipw.println("Connections (${cache.repos.size} total):")
ipw.increaseIndent()
- cache.repos.values.forEach { it.dump(ipw) }
+ cache.repos.values.forEach {
+ if (it is FullMobileConnectionRepositoryKairos) {
+ it.dump(ipw)
+ }
+ }
ipw.decreaseIndent()
}
+ private data class DumpCache(val repos: Map<Int, MobileConnectionRepositoryKairos>)
+
fun interface ConnectionRepoFactory {
fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos>
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 0eabb4ecee84..af4e61afaaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -61,31 +61,31 @@ interface MobileIconInteractor {
* consider this connection to be serving data, and thus want to show a network type icon, when
* data is connected. Other data connection states would typically cause us not to show the icon
*/
- val isDataConnected: StateFlow<Boolean>
+ val isDataConnected: Flow<Boolean>
/** True if we consider this connection to be in service, i.e. can make calls */
- val isInService: StateFlow<Boolean>
+ val isInService: Flow<Boolean>
/** True if this connection is emergency only */
- val isEmergencyOnly: StateFlow<Boolean>
+ val isEmergencyOnly: Flow<Boolean>
/** Observable for the data enabled state of this connection */
- val isDataEnabled: StateFlow<Boolean>
+ val isDataEnabled: Flow<Boolean>
/** True if the RAT icon should always be displayed and false otherwise. */
- val alwaysShowDataRatIcon: StateFlow<Boolean>
+ val alwaysShowDataRatIcon: Flow<Boolean>
/** Canonical representation of the current mobile signal strength as a triangle. */
- val signalLevelIcon: StateFlow<SignalIconModel>
+ val signalLevelIcon: Flow<SignalIconModel>
/** Observable for RAT type (network type) indicator */
- val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
+ val networkTypeIconGroup: Flow<NetworkTypeIconModel>
/** Whether or not to show the slice attribution */
- val showSliceAttribution: StateFlow<Boolean>
+ val showSliceAttribution: Flow<Boolean>
/** True if this connection is satellite-based */
- val isNonTerrestrial: StateFlow<Boolean>
+ val isNonTerrestrial: Flow<Boolean>
/**
* Provider name for this network connection. The name can be one of 3 values:
@@ -95,7 +95,7 @@ interface MobileIconInteractor {
* override in [connectionInfo.operatorAlphaShort], a value that is derived from
* [ServiceState]
*/
- val networkName: StateFlow<NetworkNameModel>
+ val networkName: Flow<NetworkNameModel>
/**
* Provider name for this network connection. The name can be one of 3 values:
@@ -108,26 +108,26 @@ interface MobileIconInteractor {
* TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
* provided is identical
*/
- val carrierName: StateFlow<String>
+ val carrierName: Flow<String>
/** True if there is only one active subscription. */
- val isSingleCarrier: StateFlow<Boolean>
+ val isSingleCarrier: Flow<Boolean>
/**
* True if this connection is considered roaming. The roaming bit can come from [ServiceState],
* or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
* connection to be roaming while carrier network change is active
*/
- val isRoaming: StateFlow<Boolean>
+ val isRoaming: Flow<Boolean>
/** See [MobileIconsInteractor.isForceHidden]. */
val isForceHidden: Flow<Boolean>
/** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
- val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+ val isAllowedDuringAirplaneMode: Flow<Boolean>
/** True when in carrier network change mode */
- val carrierNetworkChangeActive: StateFlow<Boolean>
+ val carrierNetworkChangeActive: Flow<Boolean>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
index 87877b3e9f43..6b9c5374ef2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
@@ -32,11 +32,20 @@ import com.android.systemui.kairos.map
import com.android.systemui.kairos.mapValues
import com.android.systemui.kairos.toColdConflatedFlow
import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
import javax.inject.Inject
@@ -45,6 +54,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@ExperimentalKairosApi
@@ -60,6 +71,7 @@ constructor(
context: Context,
mobileMappingsProxy: MobileMappingsProxy,
private val userSetupRepo: UserSetupRepository,
+ private val logFactory: TableLogBufferFactory,
) : MobileIconsInteractor, KairosBuilder by kairosBuilder() {
private val interactorsBySubIdK = buildIncremental {
@@ -158,7 +170,37 @@ constructor(
get() = repo.isDeviceEmergencyCallCapable
override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
- interactorsBySubId.value[subId] ?: error("Unknown subscription id: $subId")
+ object : MobileIconInteractor {
+ override val tableLogBuffer: TableLogBuffer =
+ logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
+ override val activity: Flow<DataActivityModel> = latest { activity }
+ override val mobileIsDefault: Flow<Boolean> = latest { mobileIsDefault }
+ override val isDataConnected: Flow<Boolean> = latest { isDataConnected }
+ override val isInService: Flow<Boolean> = latest { isInService }
+ override val isEmergencyOnly: Flow<Boolean> = latest { isEmergencyOnly }
+ override val isDataEnabled: Flow<Boolean> = latest { isDataEnabled }
+ override val alwaysShowDataRatIcon: Flow<Boolean> = latest { alwaysShowDataRatIcon }
+ override val signalLevelIcon: Flow<SignalIconModel> = latest { signalLevelIcon }
+ override val networkTypeIconGroup: Flow<NetworkTypeIconModel> = latest {
+ networkTypeIconGroup
+ }
+ override val showSliceAttribution: Flow<Boolean> = latest { showSliceAttribution }
+ override val isNonTerrestrial: Flow<Boolean> = latest { isNonTerrestrial }
+ override val networkName: Flow<NetworkNameModel> = latest { networkName }
+ override val carrierName: Flow<String> = latest { carrierName }
+ override val isSingleCarrier: Flow<Boolean> = latest { isSingleCarrier }
+ override val isRoaming: Flow<Boolean> = latest { isRoaming }
+ override val isForceHidden: Flow<Boolean> = latest { isForceHidden }
+ override val isAllowedDuringAirplaneMode: Flow<Boolean> = latest {
+ isAllowedDuringAirplaneMode
+ }
+ override val carrierNetworkChangeActive: Flow<Boolean> = latest {
+ carrierNetworkChangeActive
+ }
+
+ private fun <T> latest(block: MobileIconInteractor.() -> Flow<T>): Flow<T> =
+ interactorsBySubId.flatMapLatestConflated { it[subId]?.block() ?: emptyFlow() }
+ }
@dagger.Module
object Module {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 9a81992a6add..7f778bb029f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -256,6 +256,9 @@ public class Clock extends TextView implements
if (mClockFormat != null) {
mClockFormat.setTimeZone(mCalendar.getTimeZone());
}
+ if (mContentDescriptionFormat != null) {
+ mContentDescriptionFormat.setTimeZone(mCalendar.getTimeZone());
+ }
});
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
final Locale newLocale = getResources().getConfiguration().locale;
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 05b2e0d1423e..b33eafcdfa84 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -414,16 +414,15 @@ constructor(
}
}
+ private suspend fun SelectedUserModel.isEligibleForLogout(): Boolean {
+ return withContext(backgroundDispatcher) {
+ selectionStatus == SelectionStatus.SELECTION_COMPLETE &&
+ devicePolicyManager.logoutUser != null
+ }
+ }
+
companion object {
private const val TAG = "UserRepository"
@VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
}
}
-
-fun SelectedUserModel.isEligibleForLogout(): Boolean {
- // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of
- // hardcode it to USER_SYSTEM so it properly supports headless system user mode
- // (and then call mDevicePolicyManager.clearLogoutUser() after switched)
- return selectionStatus == SelectionStatus.SELECTION_COMPLETE &&
- userInfo.id != android.os.UserHandle.USER_SYSTEM
-}
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 43d1ef478ae1..32f784f17bb7 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
@@ -16,7 +16,6 @@
package com.android.systemui.volume.dialog.sliders.ui
-import android.graphics.drawable.Drawable
import android.view.View
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
@@ -29,7 +28,6 @@ import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
@@ -43,7 +41,8 @@ import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
-import com.android.compose.ui.graphics.painter.DrawablePainter
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.res.R
@@ -155,7 +154,7 @@ private fun VolumeDialogSlider(
},
)
},
- accessibilityParams = AccessibilityParams(label = sliderStateModel.label),
+ accessibilityParams = AccessibilityParams(contentDescription = sliderStateModel.label),
modifier =
modifier.pointerInput(Unit) {
coroutineScope {
@@ -172,7 +171,7 @@ private fun VolumeDialogSlider(
@Composable
private fun BoxScope.VolumeIcon(
- drawable: Drawable,
+ icon: Icon.Loaded,
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
@@ -182,6 +181,6 @@ private fun BoxScope.VolumeIcon(
exit = fadeOut(animationSpec = tween(durationMillis = 50)),
modifier = modifier.align(Alignment.Center).size(40.dp).padding(10.dp),
) {
- Icon(painter = DrawablePainter(drawable), contentDescription = null)
+ Icon(icon)
}
}
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 ef147c741bec..3712276488ff 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
@@ -18,32 +18,36 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import android.annotation.SuppressLint
import android.content.Context
-import android.graphics.drawable.Drawable
import android.media.AudioManager
import androidx.annotation.DrawableRes
import com.android.settingslib.R as SettingsR
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.withContext
@SuppressLint("UseCompatLoadingForDrawables")
class VolumeDialogSliderIconProvider
@Inject
constructor(
private val context: Context,
+ @UiBackground private val uiBackgroundContext: CoroutineContext,
private val zenModeInteractor: ZenModeInteractor,
private val audioVolumeInteractor: AudioVolumeInteractor,
) {
- fun getAudioSharingIcon(isMuted: Boolean): Flow<Drawable> {
+ fun getAudioSharingIcon(isMuted: Boolean): Flow<Icon.Loaded> {
return flow {
val iconRes =
if (isMuted) {
@@ -51,11 +55,12 @@ constructor(
} else {
R.drawable.ic_volume_media_bt
}
- emit(context.getDrawable(iconRes)!!)
+ val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! }
+ emit(Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes))
}
}
- fun getCastIcon(isMuted: Boolean): Flow<Drawable> {
+ fun getCastIcon(isMuted: Boolean): Flow<Icon.Loaded> {
return flow {
val iconRes =
if (isMuted) {
@@ -63,7 +68,8 @@ constructor(
} else {
SettingsR.drawable.ic_volume_remote
}
- emit(context.getDrawable(iconRes)!!)
+ val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! }
+ emit(Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes))
}
}
@@ -74,15 +80,18 @@ constructor(
levelMax: Int,
isMuted: Boolean,
isRoutedToBluetooth: Boolean,
- ): Flow<Drawable> {
+ ): Flow<Icon.Loaded> {
return combine(
zenModeInteractor.activeModesBlockingStream(stream),
ringerModeForStream(stream),
) { activeModesBlockingStream, ringerMode ->
if (activeModesBlockingStream?.mainMode?.icon != null) {
- return@combine activeModesBlockingStream.mainMode.icon.drawable
+ Icon.Loaded(
+ drawable = activeModesBlockingStream.mainMode.icon.drawable,
+ contentDescription = null,
+ )
} else {
- context.getDrawable(
+ val iconRes =
getIconRes(
stream,
level,
@@ -92,7 +101,8 @@ constructor(
isRoutedToBluetooth,
ringerMode,
)
- )!!
+ val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! }
+ Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
index 88a061f3813c..ed59598d97d0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import android.content.Context
-import android.graphics.drawable.Drawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.shared.model.streamLabel
@@ -25,14 +25,14 @@ data class VolumeDialogSliderStateModel(
val value: Float,
val isDisabled: Boolean,
val valueRange: ClosedFloatingPointRange<Float>,
- val icon: Drawable,
+ val icon: Icon.Loaded,
val label: String,
)
fun VolumeDialogStreamModel.toStateModel(
context: Context,
isDisabled: Boolean,
- icon: Drawable,
+ icon: Icon.Loaded,
): VolumeDialogSliderStateModel {
return VolumeDialogSliderStateModel(
value = level.toFloat(),
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 f6aa189eb571..faf0abd4cabd 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
@@ -108,11 +108,9 @@ constructor(
isMuted = isMuted,
isRoutedToBluetooth = routedToBluetooth,
)
-
is VolumeDialogSliderType.RemoteMediaStream -> {
volumeDialogSliderIconProvider.getCastIcon(isMuted)
}
-
is VolumeDialogSliderType.AudioSharingStream -> {
volumeDialogSliderIconProvider.getAudioSharingIcon(isMuted)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
index 3d98ebacc7ca..f6452679cbf4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
@@ -16,9 +16,11 @@
package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
+import android.content.Context
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.res.R
@@ -28,6 +30,7 @@ import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,11 +42,14 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
class AudioSharingStreamSliderViewModel
@AssistedInject
constructor(
+ private val context: Context,
@Assisted private val coroutineScope: CoroutineScope,
+ @UiBackground private val uiBackgroundContext: CoroutineContext,
private val audioSharingInteractor: AudioSharingInteractor,
private val uiEventLogger: UiEventLogger,
private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
@@ -51,6 +57,12 @@ constructor(
) : SliderViewModel {
private val volumeChanges = MutableStateFlow<Int?>(null)
+ private val audioSharingIcon =
+ Icon.Loaded(
+ drawable = context.getDrawable(R.drawable.ic_volume_media_bt)!!,
+ contentDescription = null,
+ res = R.drawable.ic_volume_media_bt,
+ )
override val slider: StateFlow<SliderState> =
combine(
audioSharingInteractor.volume.distinctUntilChanged().onEach {
@@ -62,16 +74,17 @@ constructor(
if (volume == null) {
SliderState.Empty
} else {
-
- State(
- value = volume.toFloat(),
- valueRange =
- audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor
- .volumeMax
- .toFloat(),
- icon = Icon.Resource(R.drawable.ic_volume_media_bt, null),
- label = deviceName,
- )
+ withContext(uiBackgroundContext) {
+ State(
+ value = volume.toFloat(),
+ valueRange =
+ audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor
+ .volumeMax
+ .toFloat(),
+ icon = audioSharingIcon,
+ label = deviceName,
+ )
+ }
}
}
.stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
@@ -107,7 +120,7 @@ constructor(
private data class State(
override val value: Float,
override val valueRange: ClosedFloatingPointRange<Float>,
- override val icon: Icon,
+ override val icon: Icon.Loaded?,
override val label: String,
) : SliderState {
override val hapticFilter: SliderHapticFeedbackFilter
@@ -116,7 +129,7 @@ constructor(
override val isEnabled: Boolean
get() = true
- override val a11yStep: Float
+ override val step: Float
get() = 1f
override val disabledMessage: String?
@@ -125,6 +138,9 @@ constructor(
override val isMutable: Boolean
get() = false
+ override val a11yContentDescription: String
+ get() = label
+
override val a11yClickDescription: String?
get() = null
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 9d32285fecb3..9fe0ad42cdba 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
@@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.Context
import android.media.AudioManager
import android.util.Log
+import androidx.annotation.DrawableRes
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -28,6 +29,7 @@ import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.modes.shared.ModesUiIcons
@@ -40,18 +42,21 @@ import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Models a particular slider state. */
class AudioStreamSliderViewModel
@@ -59,10 +64,11 @@ class AudioStreamSliderViewModel
constructor(
@Assisted private val audioStreamWrapper: FactoryAudioStreamWrapper,
@Assisted private val coroutineScope: CoroutineScope,
+ @UiBackground private val uiBackgroundContext: CoroutineContext,
private val context: Context,
private val audioVolumeInteractor: AudioVolumeInteractor,
private val zenModeInteractor: ZenModeInteractor,
- private val audioSharingInteractor: AudioSharingInteractor,
+ audioSharingInteractor: AudioSharingInteractor,
private val uiEventLogger: UiEventLogger,
private val volumePanelLogger: VolumePanelLogger,
private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
@@ -148,57 +154,69 @@ constructor(
null
}
- private fun AudioStreamModel.toState(
+ private suspend fun AudioStreamModel.toState(
isEnabled: Boolean,
ringerMode: RingerMode,
disabledMessage: String?,
inAudioSharing: Boolean,
primaryDevice: CachedBluetoothDevice?,
- ): State {
- val label = getLabel(inAudioSharing, primaryDevice)
- val icon = getIcon(ringerMode, inAudioSharing)
- return State(
- value = volume.toFloat(),
- valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
- hapticFilter = createHapticFilter(ringerMode),
- icon = icon,
- label = label,
- disabledMessage = disabledMessage,
- isEnabled = isEnabled,
- a11yStep = volumeRange.step.toFloat(),
- a11yClickDescription =
- if (isAffectedByMute) {
- context.getString(
- if (isMuted) {
- R.string.volume_panel_hint_unmute
- } else {
- R.string.volume_panel_hint_mute
- },
- label,
- )
- } else {
- null
- },
- a11yStateDescription =
- if (isMuted) {
- context.getString(
- if (isAffectedByRingerMode) {
- if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
- R.string.volume_panel_hint_vibrate
+ ): State =
+ withContext(uiBackgroundContext) {
+ val label = getLabel(inAudioSharing, primaryDevice)
+ State(
+ value = volume.toFloat(),
+ valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
+ hapticFilter = createHapticFilter(ringerMode),
+ icon = getIcon(ringerMode, inAudioSharing),
+ label = label,
+ disabledMessage = disabledMessage,
+ isEnabled = isEnabled,
+ step = volumeRange.step.toFloat(),
+ a11yContentDescription =
+ if (isEnabled) {
+ label
+ } else {
+ disabledMessage?.let {
+ context.getString(
+ R.string.volume_slider_disabled_message_template,
+ label,
+ disabledMessage,
+ )
+ } ?: label
+ },
+ a11yClickDescription =
+ if (isAffectedByMute) {
+ context.getString(
+ if (isMuted) {
+ R.string.volume_panel_hint_unmute
+ } else {
+ R.string.volume_panel_hint_mute
+ },
+ label,
+ )
+ } else {
+ null
+ },
+ a11yStateDescription =
+ if (isMuted) {
+ context.getString(
+ if (isAffectedByRingerMode) {
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.string.volume_panel_hint_vibrate
+ } else {
+ R.string.volume_panel_hint_muted
+ }
} else {
R.string.volume_panel_hint_muted
}
- } else {
- R.string.volume_panel_hint_muted
- }
- )
- } else {
- null
- },
- audioStreamModel = this,
- isMutable = isAffectedByMute,
- )
- }
+ )
+ } else {
+ null
+ },
+ audioStreamModel = this@toState,
+ isMutable = isAffectedByMute,
+ )
+ }
private fun AudioStreamModel.createHapticFilter(
ringerMode: RingerMode
@@ -220,12 +238,14 @@ constructor(
flowOf(context.getString(R.string.stream_notification_unavailable))
} else {
if (zenModeInteractor.canBeBlockedByZenMode(audioStream)) {
- zenModeInteractor.activeModesBlockingStream(audioStream).map { blockingZenModes
- ->
- blockingZenModes.mainMode?.name?.let {
- context.getString(R.string.stream_unavailable_by_modes, it)
- } ?: context.getString(R.string.stream_unavailable_by_unknown)
- }
+ zenModeInteractor
+ .activeModesBlockingStream(audioStream)
+ .map { blockingZenModes ->
+ blockingZenModes.mainMode?.name?.let {
+ context.getString(R.string.stream_unavailable_by_modes, it)
+ } ?: context.getString(R.string.stream_unavailable_by_unknown)
+ }
+ .distinctUntilChanged()
} else {
flowOf(context.getString(R.string.stream_unavailable_by_unknown))
}
@@ -256,8 +276,11 @@ constructor(
?: error("No label for the stream: $audioStream")
}
- private fun AudioStreamModel.getIcon(ringerMode: RingerMode, inAudioSharing: Boolean): Icon {
- val iconRes =
+ private fun AudioStreamModel.getIcon(
+ ringerMode: RingerMode,
+ inAudioSharing: Boolean,
+ ): Icon.Loaded {
+ val iconResource: Int =
if (isMuted) {
if (isAffectedByRingerMode) {
if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
@@ -272,14 +295,21 @@ constructor(
inAudioSharing
) {
R.drawable.ic_volume_media_bt_mute
- } else R.drawable.ic_volume_off
+ } else {
+ R.drawable.ic_volume_off
+ }
}
} else {
getIconByStream(audioStream, inAudioSharing)
}
- return Icon.Resource(iconRes, null)
+ return Icon.Loaded(
+ drawable = context.getDrawable(iconResource)!!,
+ contentDescription = null,
+ res = iconResource,
+ )
}
+ @DrawableRes
private fun getIconByStream(audioStream: AudioStream, inAudioSharing: Boolean): Int =
when (audioStream.value) {
AudioManager.STREAM_MUSIC ->
@@ -302,14 +332,15 @@ constructor(
private data class State(
override val value: Float,
override val valueRange: ClosedFloatingPointRange<Float>,
+ override val step: Float,
override val hapticFilter: SliderHapticFeedbackFilter,
- override val icon: Icon,
+ override val icon: Icon.Loaded?,
override val label: String,
override val disabledMessage: String?,
override val isEnabled: Boolean,
- override val a11yStep: Float,
override val a11yClickDescription: String?,
override val a11yStateDescription: String?,
+ override val a11yContentDescription: String,
override val isMutable: Boolean,
val audioStreamModel: AudioStreamModel,
) : SliderState
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index a6c809186ca5..01810f9aafc3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -21,6 +21,7 @@ import android.media.session.MediaController.PlaybackInfo
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.res.R
@@ -30,30 +31,40 @@ import com.android.systemui.volume.panel.shared.VolumePanelLogger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
class CastVolumeSliderViewModel
@AssistedInject
constructor(
@Assisted private val session: MediaDeviceSession,
@Assisted private val coroutineScope: CoroutineScope,
+ @UiBackground private val uiBackgroundContext: CoroutineContext,
private val context: Context,
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
private val volumePanelLogger: VolumePanelLogger,
) : SliderViewModel {
+ private val castLabel = context.getString(R.string.media_device_cast)
+ private val castIcon =
+ Icon.Loaded(
+ drawable = context.getDrawable(R.drawable.ic_cast)!!,
+ contentDescription = null,
+ res = R.drawable.ic_cast,
+ )
override val slider: StateFlow<SliderState> =
mediaDeviceSessionInteractor
.playbackInfo(session)
.mapNotNull {
volumePanelLogger.onVolumeUpdateReceived(session.sessionToken, it.currentVolume)
- it.getCurrentState()
+ withContext(uiBackgroundContext) { it.getCurrentState() }
}
.stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
@@ -83,20 +94,20 @@ constructor(
return State(
value = currentVolume.toFloat(),
valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
- icon = Icon.Resource(R.drawable.ic_cast, null),
- label = context.getString(R.string.media_device_cast),
+ icon = castIcon,
+ label = castLabel,
isEnabled = true,
- a11yStep = 1f,
+ step = 1f,
)
}
private data class State(
override val value: Float,
override val valueRange: ClosedFloatingPointRange<Float>,
- override val icon: Icon,
+ override val icon: Icon.Loaded?,
override val label: String,
override val isEnabled: Boolean,
- override val a11yStep: Float,
+ override val step: Float,
) : SliderState {
override val hapticFilter: SliderHapticFeedbackFilter
get() = SliderHapticFeedbackFilter()
@@ -107,6 +118,9 @@ constructor(
override val isMutable: Boolean
get() = false
+ override val a11yContentDescription: String
+ get() = label
+
override val a11yClickDescription: String?
get() = null
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index 4bc237bd36f5..b1d183404a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -27,18 +27,17 @@ import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
sealed interface SliderState {
val value: Float
val valueRange: ClosedFloatingPointRange<Float>
+ val step: Float
val hapticFilter: SliderHapticFeedbackFilter
- val icon: Icon?
+ // Force preloaded icon
+ val icon: Icon.Loaded?
val isEnabled: Boolean
val label: String
- /**
- * A11y slider controls works by adjusting one step up or down. The default slider step isn't
- * enough to trigger rounding to the correct value.
- */
- val a11yStep: Float
+
val a11yClickDescription: String?
val a11yStateDescription: String?
+ val a11yContentDescription: String
val disabledMessage: String?
val isMutable: Boolean
@@ -46,12 +45,13 @@ sealed interface SliderState {
override val value: Float = 0f
override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
override val hapticFilter = SliderHapticFeedbackFilter()
- override val icon: Icon? = null
+ override val icon: Icon.Loaded? = null
override val label: String = ""
override val disabledMessage: String? = null
- override val a11yStep: Float = 0f
+ override val step: Float = 0f
override val a11yClickDescription: String? = null
override val a11yStateDescription: String? = null
+ override val a11yContentDescription: String = label
override val isEnabled: Boolean = true
override val isMutable: Boolean = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
index f6582a005035..502b311f7b40 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
@@ -40,7 +40,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.clearAndSetSemantics
@@ -52,7 +51,6 @@ import androidx.compose.ui.semantics.stateDescription
import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.res.R
import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import kotlin.math.round
import kotlinx.coroutines.Job
@@ -108,7 +106,8 @@ fun Slider(
}
}
val semantics =
- accessibilityParams.createSemantics(
+ createSemantics(
+ accessibilityParams,
animatable.targetValue,
valueRange,
valueChange,
@@ -167,24 +166,18 @@ private fun snapValue(
return Math.round(coercedValue / stepDistance) * stepDistance
}
-@Composable
-private fun AccessibilityParams.createSemantics(
+private fun createSemantics(
+ params: AccessibilityParams,
value: Float,
valueRange: ClosedFloatingPointRange<Float>,
onValueChanged: (Float) -> Unit,
isEnabled: Boolean,
stepDistance: Float,
): SemanticsPropertyReceiver.() -> Unit {
- val semanticsContentDescription =
- disabledMessage
- ?.takeIf { !isEnabled }
- ?.let { message ->
- stringResource(R.string.volume_slider_disabled_message_template, label, message)
- } ?: label
return {
- contentDescription = semanticsContentDescription
+ contentDescription = params.contentDescription
if (isEnabled) {
- currentStateDescription?.let { stateDescription = it }
+ params.stateDescription?.let { stateDescription = it }
progressBarRangeInfo = ProgressBarRangeInfo(value, valueRange)
} else {
disabled()
@@ -253,9 +246,8 @@ private fun Haptics.createViewModel(
}
data class AccessibilityParams(
- val label: String,
- val currentStateDescription: String? = null,
- val disabledMessage: String? = null,
+ val contentDescription: String,
+ val stateDescription: String? = null,
)
sealed interface Haptics {
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
index 146488b523ad..108f3ae86f9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
@@ -69,4 +69,25 @@ public class MenuViewAppearanceTest extends SysuiTestCase {
assertThat(end_y).isEqualTo(end_y);
}
+
+ @Test
+ public void avoidVerticalDisplayCutout_doesNotExceedTopBounds() {
+ final int y = DRAGGABLE_BOUNDS.top - 100;
+
+ final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+ y, MENU_HEIGHT, DRAGGABLE_BOUNDS, new Rect(0, 5, 0, 6));
+
+ assertThat(end_y).isGreaterThan(DRAGGABLE_BOUNDS.top);
+ }
+
+
+ @Test
+ public void avoidVerticalDisplayCutout_doesNotExceedBottomBounds() {
+ final int y = DRAGGABLE_BOUNDS.bottom + 100;
+
+ final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+ y, MENU_HEIGHT, DRAGGABLE_BOUNDS, new Rect(0, 5, 0, 6));
+
+ assertThat(end_y).isLessThan(DRAGGABLE_BOUNDS.bottom);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index cf8278eb8ac6..82082cc778b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,9 +38,13 @@ import com.android.internal.widget.NotificationExpandButton
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.FeedbackIcon
import com.android.systemui.statusbar.notification.collection.EntryAdapter
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.msgStyleBubbleableFullPerson
+import com.android.systemui.statusbar.notification.people.peopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -51,7 +55,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
@@ -67,10 +70,12 @@ import org.mockito.MockitoAnnotations.initMocks
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class NotificationContentViewTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val factory: EntryAdapterFactory = kosmos.entryAdapterFactory
private lateinit var row: ExpandableNotificationRow
private lateinit var fakeParent: ViewGroup
- @Mock private lateinit var mPeopleNotificationIdentifier: PeopleNotificationIdentifier
private val testableResources = mContext.getOrCreateTestableResources()
private val contractedHeight =
@@ -82,24 +87,19 @@ class NotificationContentViewTest : SysuiTestCase() {
fun setup() {
initMocks(this)
fakeParent =
- spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
- val mockEntry = createMockNotificationEntry()
- val mockEntryAdapter = createMockNotificationEntryAdapter()
+ spy(FrameLayout(mContext, /* attrs= */ null)).also { it.visibility = View.GONE }
+ val entry = kosmos.msgStyleBubbleableFullPerson
+ val mockEntryAdapter = factory.create(entry)
row =
spy(
when (NotificationBundleUi.isEnabled) {
true -> {
- ExpandableNotificationRow(
- mContext,
- /* attrs= */ null,
- UserHandle.CURRENT
- ).apply {
- entryAdapter = mockEntryAdapter
- }
+ ExpandableNotificationRow(mContext, /* attrs= */ null, UserHandle.CURRENT)
+ .apply { entryAdapter = mockEntryAdapter }
}
false -> {
- ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply {
- entryLegacy = mockEntry
+ ExpandableNotificationRow(mContext, /* attrs= */ null, entry).apply {
+ entryLegacy = entry
}
}
}
@@ -406,11 +406,11 @@ class NotificationContentViewTest : SysuiTestCase() {
fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
// Bubble button should not be shown for the given NotificationEntry
- val mockNotificationEntry = createMockNotificationEntry()
+ val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson
val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
val actionListMarginTarget =
spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
- val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ val mockExpandedChild = createMockExpandedChild()
whenever(
mockExpandedChild.findViewById<LinearLayout>(
R.id.notification_action_list_margin_target
@@ -434,11 +434,11 @@ class NotificationContentViewTest : SysuiTestCase() {
fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
// Bubble button should be shown for the given NotificationEntry
- val mockNotificationEntry = createMockNotificationEntry()
+ val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson
val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
val actionListMarginTarget =
spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
- val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ val mockExpandedChild = createMockExpandedChild()
whenever(
mockExpandedChild.findViewById<LinearLayout>(
R.id.notification_action_list_margin_target
@@ -463,11 +463,11 @@ class NotificationContentViewTest : SysuiTestCase() {
@DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
- val mockNotificationEntry = createMockNotificationEntry()
+ val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson
val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
val actionListMarginTarget =
spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
- val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ val mockExpandedChild = createMockExpandedChild()
whenever(
mockExpandedChild.findViewById<LinearLayout>(
R.id.notification_action_list_margin_target
@@ -482,7 +482,7 @@ class NotificationContentViewTest : SysuiTestCase() {
// When: call NotificationContentView.onNotificationUpdated() to update the
// NotificationEntry, which should not show bubble button
- view.onNotificationUpdated(createMockNotificationEntry())
+ view.onNotificationUpdated(kosmos.msgStyleBubbleableFullPerson)
// Then: bottom margin of actionListMarginTarget should not change, still be 20
assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
@@ -492,11 +492,11 @@ class NotificationContentViewTest : SysuiTestCase() {
@DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
- val mockNotificationEntry = createMockNotificationEntry()
+ val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson
val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
val actionListMarginTarget =
spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
- val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+ val mockExpandedChild = createMockExpandedChild()
whenever(
mockExpandedChild.findViewById<LinearLayout>(
R.id.notification_action_list_margin_target
@@ -510,7 +510,7 @@ class NotificationContentViewTest : SysuiTestCase() {
// When: call NotificationContentView.onNotificationUpdated() to update the
// NotificationEntry, which should show bubble button
- view.onNotificationUpdated(createMockNotificationEntry(/*true*/ ))
+ view.onNotificationUpdated(kosmos.msgStyleBubbleableFullPerson)
// Then: no bubble yet
assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
@@ -615,15 +615,17 @@ class NotificationContentViewTest : SysuiTestCase() {
private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
mock<ExpandableNotificationRow>().apply {
- whenever(this.entry).thenReturn(notificationEntry)
+ if (!NotificationBundleUi.isEnabled) {
+ whenever(this.entryLegacy).thenReturn(notificationEntry)
+ }
whenever(this.context).thenReturn(mContext)
whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
- whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter())
+ whenever(this.entryAdapter).thenReturn(factory.create(notificationEntry))
}
private fun createMockNotificationEntry() =
mock<NotificationEntry>().apply {
- whenever(mPeopleNotificationIdentifier.getPeopleNotificationType(this))
+ whenever(kosmos.peopleNotificationIdentifier.getPeopleNotificationType(this))
.thenReturn(PeopleNotificationIdentifier.TYPE_FULL_PERSON)
whenever(this.bubbleMetadata).thenReturn(mock())
val sbnMock: StatusBarNotification = mock()
@@ -632,7 +634,8 @@ class NotificationContentViewTest : SysuiTestCase() {
whenever(sbnMock.user).thenReturn(userMock)
}
- private fun createMockNotificationEntryAdapter() = mock<EntryAdapter>()
+ private fun createMockNotificationEntryAdapter() =
+ mock<EntryAdapter>()
private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
val outerLayout = LinearLayout(mContext)
@@ -643,7 +646,7 @@ class NotificationContentViewTest : SysuiTestCase() {
return innerLayout
}
- private fun createMockExpandedChild(notificationEntry: NotificationEntry) =
+ private fun createMockExpandedChild() =
spy(createViewWithHeight(expandedHeight)).apply {
whenever(this.findViewById<ImageView>(R.id.bubble_button)).thenReturn(mock())
whenever(this.findViewById<View>(R.id.actions_container)).thenReturn(mock())
@@ -664,9 +667,16 @@ class NotificationContentViewTest : SysuiTestCase() {
val height = if (isSystemExpanded) expandedHeight else contractedHeight
doReturn(height).whenever(row).intrinsicHeight
- return spy(NotificationContentView(mContext, /* attrs= */ null))
+ return NotificationContentView(mContext, /* attrs= */ null)
.apply {
- initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock(), mock())
+ initialize(
+ kosmos.peopleNotificationIdentifier,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ )
setContainingNotification(row)
setHeights(
/* smallHeight= */ contractedHeight,
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 8fb2a245921a..6a9a485151de 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
@@ -734,6 +734,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
NotificationEntry entry = mock(NotificationEntry.class);
when(row.getEntry()).thenReturn(entry);
+ when(row.getEntryLegacy()).thenReturn(entry);
when(entry.isAmbient()).thenReturn(false);
EntryAdapter entryAdapter = mock(EntryAdapter.class);
when(entryAdapter.isAmbient()).thenReturn(false);
@@ -753,6 +754,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
NotificationEntry entry = mock(NotificationEntry.class);
when(row.getEntry()).thenReturn(entry);
+ when(row.getEntryLegacy()).thenReturn(entry);
when(entry.isAmbient()).thenReturn(true);
EntryAdapter entryAdapter = mock(EntryAdapter.class);
when(entryAdapter.isAmbient()).thenReturn(true);
@@ -772,6 +774,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
NotificationEntry entry = mock(NotificationEntry.class);
when(row.getEntry()).thenReturn(entry);
+ when(row.getEntryLegacy()).thenReturn(entry);
when(entry.isAmbient()).thenReturn(false);
EntryAdapter entryAdapter = mock(EntryAdapter.class);
when(entryAdapter.isAmbient()).thenReturn(false);
@@ -1384,6 +1387,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
NotificationEntry entry = mock(NotificationEntry.class);
when(entry.isSeenInShade()).thenReturn(true);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ when(row.getEntryLegacy()).thenReturn(entry);
when(row.getEntry()).thenReturn(entry);
// WHEN we generate an add event
@@ -1440,6 +1444,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
NotificationEntry entry = mock(NotificationEntry.class);
when(row.canViewBeCleared()).thenReturn(true);
when(row.getEntry()).thenReturn(entry);
+ when(row.getEntryLegacy()).thenReturn(entry);
when(entry.isClearable()).thenReturn(true);
EntryAdapter entryAdapter = mock(EntryAdapter.class);
when(entryAdapter.isClearable()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index a3616d20e11f..a7f3fdcb517e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -28,8 +28,8 @@ import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
import static android.provider.Settings.Global.HEADS_UP_ON;
import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE;
-import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
+import static com.android.systemui.shared.Flags.FLAG_AMBIENT_AOD;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.phone.CentralSurfaces.MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU;
@@ -242,7 +242,7 @@ import javax.inject.Provider;
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
-@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
+@EnableFlags(FLAG_AMBIENT_AOD)
public class CentralSurfacesImplTest extends SysuiTestCase {
private static final DeviceState FOLD_STATE_FOLDED = new DeviceState(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 15cb95a99967..846db6389d0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -49,11 +50,13 @@ import com.android.systemui.log.LogWtfHandlerRule;
import org.junit.After;
import org.junit.AfterClass;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.mockito.Mockito;
+import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -190,6 +193,7 @@ public abstract class SysuiTestCase {
@Before
public void SysuiSetup() throws Exception {
+ assertTempFilesAreCreatable();
ProtoLog.REQUIRE_PROTOLOGTOOL = false;
mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver());
mDependency = mSysuiDependency.install();
@@ -211,6 +215,28 @@ public abstract class SysuiTestCase {
}
}
+ private static Boolean sCanCreateTempFiles = null;
+
+ private static void assertTempFilesAreCreatable() {
+ // TODO(b/391948934): hopefully remove this hack
+ if (sCanCreateTempFiles == null) {
+ try {
+ File tempFile = File.createTempFile("confirm_temp_file_createable", "txt");
+ sCanCreateTempFiles = true;
+ assertTrue(tempFile.delete());
+ } catch (IOException e) {
+ sCanCreateTempFiles = false;
+ throw new RuntimeException(e);
+ }
+ }
+ if (!sCanCreateTempFiles) {
+ Assert.fail(
+ "Cannot create temp files, so mockito will probably fail (b/391948934). Temp"
+ + " folder should be: "
+ + System.getProperty("java.io.tmpdir"));
+ }
+ }
+
protected boolean shouldFailOnLeakedReceiver() {
return false;
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 338e4bec7aa2..122e6a507cbf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.display.data.repository
import android.view.Display
+import com.android.app.displaylib.DisplayRepository.PendingDisplay
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.mockito.mock
import dagger.Binds
@@ -26,7 +27,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import com.android.app.displaylib.DisplayRepository.PendingDisplay
import org.mockito.Mockito.`when` as whenever
/** Creates a mock display. */
@@ -50,8 +50,7 @@ fun createPendingDisplay(id: Int = 0): PendingDisplay =
class FakeDisplayRepository @Inject constructor() : DisplayRepository {
private val flow = MutableStateFlow<Set<Display>>(emptySet())
private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet())
- private val pendingDisplayFlow =
- MutableSharedFlow<PendingDisplay?>(replay = 1)
+ private val pendingDisplayFlow = MutableSharedFlow<PendingDisplay?>(replay = 1)
private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0)
private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet())
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
index 5ab3b3de49f4..4b516e9c74bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
@@ -16,6 +16,8 @@
package com.android.systemui.display.data.repository
+import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 623989ec5809..c80d7386f67a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -81,6 +81,7 @@ import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlag
import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.row.entryAdapterFactory
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.fakeAutoHideControllerStore
@@ -206,4 +207,5 @@ class KosmosJavaAdapter() {
val displayTracker by lazy { kosmos.displayTracker }
val fakeShadeDisplaysRepository by lazy { kosmos.fakeShadeDisplaysRepository }
val sysUIStateDispatcher by lazy { kosmos.sysUIStateDispatcher }
+ val entryAdapterFactory by lazy { kosmos.entryAdapterFactory }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt
index 59f5ecd2563f..00c6c9445fe7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt
@@ -14,18 +14,25 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification
+package com.android.systemui.statusbar.notification.collection
import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.PendingIntent
import android.app.Person
import android.content.Intent
import android.content.applicationContext
+import android.graphics.Bitmap
import android.graphics.drawable.Icon
+import com.android.systemui.activity.EmptyTestActivity
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.icon.IconPack
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.promoted.setPromotedContent
import org.mockito.kotlin.mock
@@ -40,6 +47,11 @@ fun Kosmos.setIconPackWithMockIconViews(entry: NotificationEntry) {
)
}
+fun Kosmos.buildPromotedOngoingEntry(
+ block: NotificationEntryBuilder.() -> Unit = {}
+): NotificationEntry =
+ buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block)
+
fun Kosmos.buildOngoingCallEntry(
promoted: Boolean = false,
block: NotificationEntryBuilder.() -> Unit = {},
@@ -51,11 +63,6 @@ fun Kosmos.buildOngoingCallEntry(
block = block,
)
-fun Kosmos.buildPromotedOngoingEntry(
- block: NotificationEntryBuilder.() -> Unit = {}
-): NotificationEntry =
- buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block)
-
fun Kosmos.buildNotificationEntry(
tag: String? = null,
promoted: Boolean = false,
@@ -88,3 +95,49 @@ private fun Kosmos.makeOngoingCallStyle(): Notification.CallStyle {
val person = Person.Builder().setName("person").build()
return Notification.CallStyle.forOngoingCall(person, pendingIntent)
}
+
+private fun Kosmos.makeMessagingStyleNotification(): Notification.Builder {
+ val personIcon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+ val message = Notification.MessagingStyle.Message("Message!", 4323, person)
+ val bubbleIntent =
+ PendingIntent.getActivity(
+ applicationContext,
+ 0,
+ Intent(applicationContext, EmptyTestActivity::class.java),
+ PendingIntent.FLAG_MUTABLE,
+ )
+
+ return Notification.Builder(applicationContext, "channelId")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text")
+ .setStyle(Notification.MessagingStyle(person).addMessage(message))
+ .setBubbleMetadata(
+ Notification.BubbleMetadata.Builder(
+ bubbleIntent,
+ Icon.createWithResource(applicationContext, R.drawable.android),
+ )
+ .setDeleteIntent(mock<PendingIntent>())
+ .setDesiredHeight(314)
+ .setAutoExpandBubble(false)
+ .build()
+ )
+}
+
+fun Kosmos.makeEntryOfPeopleType(@PeopleNotificationType type: Int): NotificationEntryBuilder {
+ val channel = NotificationChannel("messages", "messages", IMPORTANCE_DEFAULT)
+ channel.isImportantConversation = (type == TYPE_IMPORTANT_PERSON)
+ channel.setConversationId("parent", "convo")
+
+ val entry =
+ NotificationEntryBuilder().apply {
+ updateRanking {
+ it.setIsConversation(type != TYPE_NON_PERSON)
+ it.setShortcutInfo(if (type >= TYPE_FULL_PERSON) mock() else null)
+ it.setChannel(channel)
+ }
+ setNotification(makeMessagingStyleNotification().build())
+ }
+ return entry
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt
new file mode 100644
index 000000000000..e127a70dc07d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt
@@ -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 com.android.systemui.statusbar.notification.collection
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
+
+val Kosmos.msgStyleBubbleableFullPerson by
+ Kosmos.Fixture { makeEntryOfPeopleType(TYPE_FULL_PERSON).build() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
index 09f9f1c6362e..44d7a22b6258 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
@@ -25,6 +26,7 @@ val Kosmos.volumeDialogSliderIconProvider by
Kosmos.Fixture {
VolumeDialogSliderIconProvider(
context = applicationContext,
+ uiBackgroundContext = backgroundCoroutineContext,
audioVolumeInteractor = audioVolumeInteractor,
zenModeInteractor = zenModeInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
index 8c8d0240f572..6e43d79295ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
@@ -16,9 +16,11 @@
package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
+import android.content.applicationContext
import com.android.internal.logging.uiEventLogger
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.volume.domain.interactor.audioSharingInteractor
import com.android.systemui.volume.shared.volumePanelLogger
import kotlinx.coroutines.CoroutineScope
@@ -28,7 +30,9 @@ val Kosmos.audioSharingStreamSliderViewModelFactory by
object : AudioSharingStreamSliderViewModel.Factory {
override fun create(coroutineScope: CoroutineScope): AudioSharingStreamSliderViewModel {
return AudioSharingStreamSliderViewModel(
+ applicationContext,
coroutineScope,
+ backgroundCoroutineContext,
audioSharingInteractor,
uiEventLogger,
sliderHapticsViewModelFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
index 88c716e0ab10..47016e535ea4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
@@ -20,6 +20,7 @@ import android.content.applicationContext
import com.android.internal.logging.uiEventLogger
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.volume.domain.interactor.audioSharingInteractor
import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
@@ -37,6 +38,7 @@ val Kosmos.audioStreamSliderViewModelFactory by
return AudioStreamSliderViewModel(
audioStream,
coroutineScope,
+ backgroundCoroutineContext,
applicationContext,
audioVolumeInteractor,
zenModeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
index 6875619d45fc..ed51e054e50c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.applicationContext
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
import com.android.systemui.volume.shared.volumePanelLogger
@@ -34,6 +35,7 @@ val Kosmos.castVolumeSliderViewModelFactory by
return CastVolumeSliderViewModel(
session,
coroutineScope,
+ backgroundCoroutineContext,
applicationContext,
mediaDeviceSessionInteractor,
sliderHapticsViewModelFactory,
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
index b956e44e0618..90a92712bf81 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
@@ -281,7 +281,6 @@ internal class DepthTracker {
},
)
}
- downstreamSet.clear()
}
}
reset()
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
index c11eb122597d..81f37022a868 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
@@ -145,7 +145,14 @@ internal class MuxDeferredNode<W, K, V>(
val conn = branchNode.upstream
severed.add(conn)
conn.removeDownstream(downstream = branchNode.schedulable)
- depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+ if (conn.depthTracker.snapshotIsDirect) {
+ depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+ } else {
+ depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+ depthTracker.updateIndirectRoots(
+ removals = conn.depthTracker.snapshotIndirectRoots
+ )
+ }
}
}
@@ -156,7 +163,14 @@ internal class MuxDeferredNode<W, K, V>(
val conn = branchNode.upstream
severed.add(conn)
conn.removeDownstream(downstream = branchNode.schedulable)
- depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+ if (conn.depthTracker.snapshotIsDirect) {
+ depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+ } else {
+ depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+ depthTracker.updateIndirectRoots(
+ removals = conn.depthTracker.snapshotIndirectRoots
+ )
+ }
}
// add new
@@ -343,13 +357,8 @@ private class MuxDeferredActivator<W, K, V>(
val (patchesConn, needsEval) =
getPatches(evalScope).activate(evalScope, downstream = muxNode.schedulable)
?: run {
- // Turns out we can't connect to patches, so update our depth and
- // propagate
- if (muxNode.depthTracker.setIsIndirectRoot(false)) {
- // TODO: schedules might not be necessary now that we're not
- // parallel?
- muxNode.depthTracker.schedule(evalScope.scheduler, muxNode)
- }
+ // Turns out we can't connect to patches, so update our depth
+ muxNode.depthTracker.setIsIndirectRoot(false)
return
}
muxNode.patches = patchesConn
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
index cb2c6e51df66..faef6a21d4f3 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
@@ -109,7 +109,14 @@ internal class MuxPromptNode<W, K, V>(
val conn: NodeConnection<V> = branchNode.upstream
severed.add(conn)
conn.removeDownstream(downstream = branchNode.schedulable)
- depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+ if (conn.depthTracker.snapshotIsDirect) {
+ depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+ } else {
+ depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+ depthTracker.updateIndirectRoots(
+ removals = conn.depthTracker.snapshotIndirectRoots
+ )
+ }
}
}
@@ -123,7 +130,14 @@ internal class MuxPromptNode<W, K, V>(
val conn: NodeConnection<V> = oldBranch.upstream
severed.add(conn)
conn.removeDownstream(downstream = oldBranch.schedulable)
- depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+ if (conn.depthTracker.snapshotIsDirect) {
+ depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+ } else {
+ depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+ depthTracker.updateIndirectRoots(
+ removals = conn.depthTracker.snapshotIndirectRoots
+ )
+ }
}
// add new
diff --git a/ravenwood/tools/hoststubgen/Android.bp b/ravenwood/tools/hoststubgen/Android.bp
index 004834eed983..e605318f4a10 100644
--- a/ravenwood/tools/hoststubgen/Android.bp
+++ b/ravenwood/tools/hoststubgen/Android.bp
@@ -99,6 +99,7 @@ java_library_host {
"ow2-asm-commons",
"ow2-asm-tree",
"ow2-asm-util",
+ "apache-commons-compress",
],
}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt
index b2af7827f8c5..a62f66dd18e5 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt
@@ -16,6 +16,15 @@
package com.android.hoststubgen
import java.io.PrintWriter
+import java.util.zip.CRC32
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
+import org.apache.commons.compress.archivers.zip.ZipFile
+
+/**
+ * Whether to skip compression when adding processed entries back to a zip file.
+ */
+private const val SKIP_COMPRESSION = false
/**
* Name of this executable. Set it in the main method.
@@ -118,3 +127,29 @@ inline fun runMainWithBoilerplate(realMain: () -> Unit) {
System.exit(if (success) 0 else 1 )
}
+
+/**
+ * Copy a single ZIP entry to the output.
+ */
+fun copyZipEntry(
+ inZip: ZipFile,
+ entry: ZipArchiveEntry,
+ out: ZipArchiveOutputStream,
+) {
+ inZip.getRawInputStream(entry).use { out.addRawArchiveEntry(entry, it) }
+}
+
+/**
+ * Add a single ZIP entry with data.
+ */
+fun ZipArchiveOutputStream.addBytesEntry(name: String, data: ByteArray) {
+ val newEntry = ZipArchiveEntry(name)
+ if (SKIP_COMPRESSION) {
+ newEntry.method = 0
+ newEntry.size = data.size.toLong()
+ newEntry.crc = CRC32().apply { update(data) }.value
+ }
+ putArchiveEntry(newEntry)
+ write(data)
+ closeArchiveEntry()
+}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt
index e2647eb13ed3..40d343ab9658 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt
@@ -18,21 +18,20 @@ package com.android.hoststubgen.asm
import com.android.hoststubgen.ClassParseException
import com.android.hoststubgen.InvalidJarFileException
import com.android.hoststubgen.log
-import org.objectweb.asm.ClassReader
-import org.objectweb.asm.tree.AnnotationNode
-import org.objectweb.asm.tree.ClassNode
-import org.objectweb.asm.tree.FieldNode
-import org.objectweb.asm.tree.MethodNode
-import org.objectweb.asm.tree.TypeAnnotationNode
-import java.io.BufferedInputStream
import java.io.PrintWriter
import java.util.Arrays
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import java.util.function.Consumer
-import java.util.zip.ZipEntry
-import java.util.zip.ZipFile
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipFile
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.tree.AnnotationNode
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldNode
+import org.objectweb.asm.tree.MethodNode
+import org.objectweb.asm.tree.TypeAnnotationNode
/**
* Stores all classes loaded from a jar file, in a form of [ClassNode]
@@ -189,7 +188,8 @@ class ClassNodes {
* Load all the classes, without code.
*/
fun loadClassStructures(
- inJar: String,
+ inJar: ZipFile,
+ jarName: String,
timeCollector: Consumer<Double>? = null,
): ClassNodes {
val allClasses = ClassNodes()
@@ -201,20 +201,19 @@ class ClassNodes {
val exception = AtomicReference<Throwable>()
// Called on a BG thread. Read a single jar entry and add it to [allClasses].
- fun parseClass(inZip: ZipFile, entry: ZipEntry) {
+ fun parseClass(inZip: ZipFile, entry: ZipArchiveEntry) {
try {
- inZip.getInputStream(entry).use { ins ->
- val cr = ClassReader(BufferedInputStream(ins))
- val cn = ClassNode()
- cr.accept(
- cn, ClassReader.SKIP_CODE
- or ClassReader.SKIP_DEBUG
- or ClassReader.SKIP_FRAMES
- )
- synchronized(allClasses) {
- if (!allClasses.addClass(cn)) {
- log.w("Duplicate class found: ${cn.name}")
- }
+ val classBytes = inZip.getInputStream(entry).use { it.readAllBytes() }
+ val cr = ClassReader(classBytes)
+ val cn = ClassNode()
+ cr.accept(
+ cn, ClassReader.SKIP_CODE
+ or ClassReader.SKIP_DEBUG
+ or ClassReader.SKIP_FRAMES
+ )
+ synchronized(allClasses) {
+ if (!allClasses.addClass(cn)) {
+ log.w("Duplicate class found: ${cn.name}")
}
}
} catch (e: Throwable) {
@@ -224,35 +223,30 @@ class ClassNodes {
}
// Actually open the jar and read it on worker threads.
- val time = log.iTime("Reading class structure from $inJar") {
+ val time = log.iTime("Reading class structure from $jarName") {
log.withIndent {
- ZipFile(inJar).use { inZip ->
- val inEntries = inZip.entries()
-
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
-
- if (entry.name.endsWith(".class")) {
- executor.submit {
- parseClass(inZip, entry)
- }
- } else if (entry.name.endsWith(".dex")) {
- // Seems like it's an ART jar file. We can't process it.
- // It's a fatal error.
- throw InvalidJarFileException(
- "$inJar is not a desktop jar file."
- + " It contains a *.dex file."
- )
- } else {
- // Unknown file type. Skip.
+ inJar.entries.asSequence().forEach { entry ->
+ if (entry.name.endsWith(".class")) {
+ executor.submit {
+ parseClass(inJar, entry)
}
+ } else if (entry.name.endsWith(".dex")) {
+ // Seems like it's an ART jar file. We can't process it.
+ // It's a fatal error.
+ throw InvalidJarFileException(
+ "$jarName is not a desktop jar file."
+ + " It contains a *.dex file."
+ )
+ } else {
+ // Unknown file type. Skip.
}
- // Wait for all the work to complete. (must do it before closing the zip)
- log.i("Waiting for all loaders to finish...")
- executor.shutdown()
- executor.awaitTermination(5, TimeUnit.MINUTES)
- log.i("All loaders to finished.")
}
+
+ // Wait for all the work to complete. (must do it before closing the zip)
+ log.i("Waiting for all loaders to finish...")
+ executor.shutdown()
+ executor.awaitTermination(5, TimeUnit.MINUTES)
+ log.i("All loaders to finished.")
}
// If any exception is detected, throw it.
@@ -261,13 +255,13 @@ class ClassNodes {
}
if (allClasses.size == 0) {
- log.w("$inJar contains no *.class files.")
+ log.w("$jarName contains no *.class files.")
} else {
- log.i("Loaded ${allClasses.size} classes from $inJar.")
+ log.i("Loaded ${allClasses.size} classes from $jarName.")
}
}
timeCollector?.accept(time)
return allClasses
}
}
-} \ No newline at end of file
+}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 333540573364..2edcb2a6c199 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -19,13 +19,11 @@ import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.dumper.ApiDumper
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.printAsTextPolicy
-import java.io.BufferedInputStream
-import java.io.BufferedOutputStream
import java.io.FileOutputStream
import java.io.PrintWriter
-import java.util.zip.ZipEntry
-import java.util.zip.ZipFile
-import java.util.zip.ZipOutputStream
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
+import org.apache.commons.compress.archivers.zip.ZipFile
/**
* Actual main class.
@@ -34,9 +32,10 @@ class HostStubGen(val options: HostStubGenOptions) {
fun run() {
val errors = HostStubGenErrors()
val stats = HostStubGenStats()
+ val inJar = ZipFile(options.inJar.get)
// Load all classes.
- val allClasses = ClassNodes.loadClassStructures(options.inJar.get)
+ val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get)
// Dump the classes, if specified.
options.inputJarDumpFile.ifSet {
@@ -59,7 +58,7 @@ class HostStubGen(val options: HostStubGenOptions) {
val processor = HostStubGenClassProcessor(options, allClasses, errors, stats)
// Transform the jar.
- convert(
+ inJar.convert(
options.inJar.get,
options.outJar.get,
processor,
@@ -88,7 +87,7 @@ class HostStubGen(val options: HostStubGenOptions) {
/**
* Convert a JAR file into "stub" and "impl" JAR files.
*/
- private fun convert(
+ private fun ZipFile.convert(
inJar: String,
outJar: String?,
processor: HostStubGenClassProcessor,
@@ -100,45 +99,39 @@ class HostStubGen(val options: HostStubGenOptions) {
log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
log.iTime("Transforming jar") {
- var itemIndex = 0
var numItemsProcessed = 0
var numItems = -1 // == Unknown
log.withIndent {
- // Open the input jar file and process each entry.
- ZipFile(inJar).use { inZip ->
-
- numItems = inZip.size()
- val shardStart = numItems * shard / numShards
- val shardNextStart = numItems * (shard + 1) / numShards
-
- maybeWithZipOutputStream(outJar) { outStream ->
- val inEntries = inZip.entries()
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
- val inShard = (shardStart <= itemIndex)
- && (itemIndex < shardNextStart)
- itemIndex++
- if (!inShard) {
- continue
- }
- convertSingleEntry(inZip, entry, outStream, processor)
- numItemsProcessed++
+ val entries = entries.toList()
+
+ numItems = entries.size
+ val shardStart = numItems * shard / numShards
+ val shardNextStart = numItems * (shard + 1) / numShards
+
+ maybeWithZipOutputStream(outJar) { outStream ->
+ entries.forEachIndexed { itemIndex, entry ->
+ val inShard = (shardStart <= itemIndex)
+ && (itemIndex < shardNextStart)
+ if (!inShard) {
+ return@forEachIndexed
}
- log.i("Converted all entries.")
+ convertSingleEntry(this, entry, outStream, processor)
+ numItemsProcessed++
}
- outJar?.let { log.i("Created: $it") }
+ log.i("Converted all entries.")
}
+ outJar?.let { log.i("Created: $it") }
}
log.i("%d / %d item(s) processed.", numItemsProcessed, numItems)
}
}
- private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
+ private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipArchiveOutputStream?) -> T): T {
if (filename == null) {
return block(null)
}
- return ZipOutputStream(BufferedOutputStream(FileOutputStream(filename))).use(block)
+ return ZipArchiveOutputStream(FileOutputStream(filename).buffered()).use(block)
}
/**
@@ -146,8 +139,8 @@ class HostStubGen(val options: HostStubGenOptions) {
*/
private fun convertSingleEntry(
inZip: ZipFile,
- entry: ZipEntry,
- outStream: ZipOutputStream?,
+ entry: ZipArchiveEntry,
+ outStream: ZipArchiveOutputStream?,
processor: HostStubGenClassProcessor
) {
log.d("Entry: %s", entry.name)
@@ -181,32 +174,12 @@ class HostStubGen(val options: HostStubGenOptions) {
}
/**
- * Copy a single ZIP entry to the output.
- */
- private fun copyZipEntry(
- inZip: ZipFile,
- entry: ZipEntry,
- out: ZipOutputStream,
- ) {
- // TODO: It seems like copying entries this way is _very_ slow,
- // even with out.setLevel(0). Look for other ways to do it.
-
- inZip.getInputStream(entry).use { ins ->
- // Copy unknown entries as is to the impl out. (but not to the stub out.)
- val outEntry = ZipEntry(entry.name)
- out.putNextEntry(outEntry)
- ins.transferTo(out)
- out.closeEntry()
- }
- }
-
- /**
* Convert a single class.
*/
private fun processSingleClass(
inZip: ZipFile,
- entry: ZipEntry,
- outStream: ZipOutputStream?,
+ entry: ZipArchiveEntry,
+ outStream: ZipArchiveOutputStream?,
processor: HostStubGenClassProcessor
) {
val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "")
@@ -227,12 +200,10 @@ class HostStubGen(val options: HostStubGenOptions) {
if (outStream != null) {
log.v("Creating class: %s Policy: %s", classInternalName, classPolicy)
log.withIndent {
- BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- val newEntry = ZipEntry(newName)
- outStream.putNextEntry(newEntry)
- val classBytecode = bis.readAllBytes()
- outStream.write(processor.processClassBytecode(classBytecode))
- outStream.closeEntry()
+ inZip.getInputStream(entry).use { zis ->
+ var classBytecode = zis.readAllBytes()
+ classBytecode = processor.processClassBytecode(classBytecode)
+ outStream.addBytesEntry(newName, classBytecode)
}
}
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index 04e3bda2ba27..8e36323fd495 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -17,17 +17,17 @@ package com.android.platform.test.ravenwood.ravenizer
import com.android.hoststubgen.GeneralUserErrorException
import com.android.hoststubgen.HostStubGenClassProcessor
+import com.android.hoststubgen.addBytesEntry
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.zipEntryNameToClassName
+import com.android.hoststubgen.copyZipEntry
import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
import com.android.platform.test.ravenwood.ravenizer.adapter.RunnerRewritingAdapter
-import java.io.BufferedInputStream
-import java.io.BufferedOutputStream
import java.io.FileOutputStream
-import java.util.zip.ZipEntry
-import java.util.zip.ZipFile
-import java.util.zip.ZipOutputStream
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
+import org.apache.commons.compress.archivers.zip.ZipFile
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
@@ -93,13 +93,14 @@ class Ravenizer {
val stats = RavenizerStats()
stats.totalTime = log.nTime {
- val allClasses = ClassNodes.loadClassStructures(options.inJar.get) {
+ val inJar = ZipFile(options.inJar.get)
+ val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get) {
stats.loadStructureTime = it
}
val processor = HostStubGenClassProcessor(options, allClasses)
- process(
- options.inJar.get,
+ inJar.process(
+ options.outJar.get,
options.outJar.get,
options.enableValidation.get,
options.fatalValidation.get,
@@ -111,7 +112,7 @@ class Ravenizer {
log.i(stats.toString())
}
- private fun process(
+ private fun ZipFile.process(
inJar: String,
outJar: String,
enableValidation: Boolean,
@@ -138,40 +139,34 @@ class Ravenizer {
}
stats.totalProcessTime = log.vTime("$executableName processing $inJar") {
- ZipFile(inJar).use { inZip ->
- val inEntries = inZip.entries()
-
- stats.totalEntries = inZip.size()
-
- ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip ->
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
-
- if (entry.name.endsWith(".dex")) {
- // Seems like it's an ART jar file. We can't process it.
- // It's a fatal error.
- throw GeneralUserErrorException(
- "$inJar is not a desktop jar file. It contains a *.dex file."
- )
- }
+ ZipArchiveOutputStream(FileOutputStream(outJar).buffered()).use { outZip ->
+ entries.asSequence().forEach { entry ->
+ stats.totalEntries++
+ if (entry.name.endsWith(".dex")) {
+ // Seems like it's an ART jar file. We can't process it.
+ // It's a fatal error.
+ throw GeneralUserErrorException(
+ "$inJar is not a desktop jar file. It contains a *.dex file."
+ )
+ }
- if (stripMockito && entry.name.isMockitoFile()) {
- // Skip this entry
- continue
- }
+ if (stripMockito && entry.name.isMockitoFile()) {
+ // Skip this entry
+ return@forEach
+ }
- val className = zipEntryNameToClassName(entry.name)
+ val className = zipEntryNameToClassName(entry.name)
- if (className != null) {
- stats.totalClasses += 1
- }
+ if (className != null) {
+ stats.totalClasses += 1
+ }
- if (className != null &&
- shouldProcessClass(processor.allClasses, className)) {
- processSingleClass(inZip, entry, outZip, processor, stats)
- } else {
- // Too slow, let's use merge_zips to bring back the original classes.
- copyZipEntry(inZip, entry, outZip, stats)
+ if (className != null &&
+ shouldProcessClass(processor.allClasses, className)) {
+ processSingleClass(this, entry, outZip, processor, stats)
+ } else {
+ stats.totalCopyTime += log.nTime {
+ copyZipEntry(this, entry, outZip)
}
}
}
@@ -179,53 +174,25 @@ class Ravenizer {
}
}
- /**
- * Copy a single ZIP entry to the output.
- */
- private fun copyZipEntry(
- inZip: ZipFile,
- entry: ZipEntry,
- out: ZipOutputStream,
- stats: RavenizerStats,
- ) {
- stats.totalCopyTime += log.nTime {
- inZip.getInputStream(entry).use { ins ->
- // Copy unknown entries as is to the impl out. (but not to the stub out.)
- val outEntry = ZipEntry(entry.name)
- outEntry.method = 0
- outEntry.size = entry.size
- outEntry.crc = entry.crc
- out.putNextEntry(outEntry)
-
- ins.transferTo(out)
-
- out.closeEntry()
- }
- }
- }
-
private fun processSingleClass(
inZip: ZipFile,
- entry: ZipEntry,
- outZip: ZipOutputStream,
+ entry: ZipArchiveEntry,
+ outZip: ZipArchiveOutputStream,
processor: HostStubGenClassProcessor,
stats: RavenizerStats,
) {
stats.processedClasses += 1
- val newEntry = ZipEntry(entry.name)
- outZip.putNextEntry(newEntry)
-
- BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- var classBytes = bis.readBytes()
+ inZip.getInputStream(entry).use { zis ->
+ var classBytes = zis.readAllBytes()
stats.totalRavenizeTime += log.vTime("Ravenize ${entry.name}") {
classBytes = ravenizeSingleClass(entry, classBytes, processor.allClasses)
}
stats.totalHostStubGenTime += log.vTime("HostStubGen ${entry.name}") {
classBytes = processor.processClassBytecode(classBytes)
}
- outZip.write(classBytes)
+ // TODO: if the class does not change, use copyZipEntry
+ outZip.addBytesEntry(entry.name, classBytes)
}
- outZip.closeEntry()
}
/**
@@ -237,7 +204,7 @@ class Ravenizer {
}
private fun ravenizeSingleClass(
- entry: ZipEntry,
+ entry: ZipArchiveEntry,
input: ByteArray,
allClasses: ClassNodes,
): ByteArray {
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 658ea4c27e4c..a4cbf420b93b 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -168,7 +168,6 @@ public class AdbDebuggingManager {
private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo();
// Polls for a tls port property when adb wifi is enabled
private AdbConnectionPortPoller mConnectionPortPoller;
- private final PortListenerImpl mPortListener = new PortListenerImpl();
private final Ticker mTicker;
public AdbDebuggingManager(Context context) {
@@ -323,10 +322,6 @@ public class AdbDebuggingManager {
}
}
- interface AdbConnectionPortListener {
- void onPortReceived(int port);
- }
-
/**
* This class will poll for a period of time for adbd to write the port
* it connected to.
@@ -336,16 +331,11 @@ public class AdbDebuggingManager {
* port through different means. A better fix would be to always start AdbDebuggingManager, but
* it needs to adjust accordingly on whether ro.adb.secure is set.
*/
- static class AdbConnectionPortPoller extends Thread {
+ private class AdbConnectionPortPoller extends Thread {
private final String mAdbPortProp = "service.adb.tls.port";
- private AdbConnectionPortListener mListener;
private final int mDurationSecs = 10;
private AtomicBoolean mCanceled = new AtomicBoolean(false);
- AdbConnectionPortPoller(AdbConnectionPortListener listener) {
- mListener = listener;
- }
-
@Override
public void run() {
Slog.d(TAG, "Starting adb port property poller");
@@ -362,13 +352,22 @@ public class AdbDebuggingManager {
// to start the server. Otherwise we should have a valid port.
int port = SystemProperties.getInt(mAdbPortProp, Integer.MAX_VALUE);
if (port == -1 || (port > 0 && port <= 65535)) {
- mListener.onPortReceived(port);
+ onPortReceived(port);
return;
}
SystemClock.sleep(1000);
}
Slog.w(TAG, "Failed to receive adb connection port");
- mListener.onPortReceived(-1);
+ onPortReceived(-1);
+ }
+
+ private void onPortReceived(int port) {
+ Slog.d(TAG, "Received tls port=" + port);
+ Message msg = mHandler.obtainMessage(port > 0
+ ? AdbDebuggingHandler.MSG_SERVER_CONNECTED
+ : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED);
+ msg.obj = port;
+ mHandler.sendMessage(msg);
}
public void cancelAndWait() {
@@ -382,17 +381,6 @@ public class AdbDebuggingManager {
}
}
- class PortListenerImpl implements AdbConnectionPortListener {
- public void onPortReceived(int port) {
- Slog.d(TAG, "Received tls port=" + port);
- Message msg = mHandler.obtainMessage(port > 0
- ? AdbDebuggingHandler.MSG_SERVER_CONNECTED
- : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED);
- msg.obj = port;
- mHandler.sendMessage(msg);
- }
- }
-
@VisibleForTesting
static class AdbDebuggingThread extends Thread {
private boolean mStopped;
@@ -1088,8 +1076,7 @@ public class AdbDebuggingManager {
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
- mConnectionPortPoller =
- new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+ mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller();
mConnectionPortPoller.start();
startAdbDebuggingThread();
@@ -1138,8 +1125,7 @@ public class AdbDebuggingManager {
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
- mConnectionPortPoller =
- new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+ mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller();
mConnectionPortPoller.start();
startAdbDebuggingThread();
@@ -1257,7 +1243,7 @@ public class AdbDebuggingManager {
if (mAdbWifiEnabled) {
// In scenarios where adbd is restarted, the tls port may change.
mConnectionPortPoller =
- new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+ new AdbDebuggingManager.AdbConnectionPortPoller();
mConnectionPortPoller.start();
}
break;
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 40f7c873eae8..d12a0a2a1e00 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -21,10 +21,8 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
-import android.debug.AdbManager;
import android.debug.AdbManagerInternal;
import android.debug.AdbTransportType;
import android.debug.FingerprintAndPairDevice;
@@ -40,10 +38,8 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings;
import android.service.adb.AdbServiceDumpProto;
-import android.sysprop.AdbProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -63,7 +59,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* The Android Debug Bridge (ADB) service. This controls the availability of ADB and authorization
@@ -85,12 +80,6 @@ public class AdbService extends IAdbManager.Stub {
*/
static final String CTL_STOP = "ctl.stop";
- // The tcp port adb is currently using
- AtomicInteger mConnectionPort = new AtomicInteger(-1);
-
- private final AdbConnectionPortListener mPortListener = new AdbConnectionPortListener();
- private AdbDebuggingManager.AdbConnectionPortPoller mConnectionPortPoller;
-
private final RemoteCallbackList<IAdbCallback> mCallbacks = new RemoteCallbackList<>();
/**
* Manages the service lifecycle for {@code AdbService} in {@code SystemServer}.
@@ -404,39 +393,6 @@ public class AdbService extends IAdbManager.Stub {
Slog.d(TAG, "Unregistering callback " + callback);
mCallbacks.unregister(callback);
}
- /**
- * This listener is only used when ro.adb.secure=0. Otherwise, AdbDebuggingManager will
- * do this.
- */
- class AdbConnectionPortListener implements AdbDebuggingManager.AdbConnectionPortListener {
- public void onPortReceived(int port) {
- if (port > 0 && port <= 65535) {
- mConnectionPort.set(port);
- } else {
- mConnectionPort.set(-1);
- // Turn off wifi debugging, since the server did not start.
- try {
- Settings.Global.putInt(mContentResolver,
- Settings.Global.ADB_WIFI_ENABLED, 0);
- } catch (SecurityException e) {
- // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't
- // be changed.
- Slog.d(TAG, "ADB_ENABLED is restricted.");
- }
- }
- broadcastPortInfo(mConnectionPort.get());
- }
- }
-
- private void broadcastPortInfo(int port) {
- Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
- intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, (port >= 0)
- ? AdbManager.WIRELESS_STATUS_CONNECTED
- : AdbManager.WIRELESS_STATUS_DISCONNECTED);
- intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
- AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
- Slog.i(TAG, "sent port broadcast port=" + port);
- }
private void startAdbd() {
SystemProperties.set(CTL_START, ADBD);
@@ -470,20 +426,11 @@ public class AdbService extends IAdbManager.Stub {
} else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) {
mIsAdbWifiEnabled = enable;
if (mIsAdbWifiEnabled) {
- if (!AdbProperties.secure().orElse(false)) {
- // Start adbd. If this is secure adb, then we defer enabling adb over WiFi.
- SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
- mConnectionPortPoller =
- new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
- mConnectionPortPoller.start();
- }
+ // Start adb over WiFi.
+ SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
} else {
// Stop adb over WiFi.
SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "0");
- if (mConnectionPortPoller != null) {
- mConnectionPortPoller.cancelAndWait();
- mConnectionPortPoller = null;
- }
}
} else {
// No change
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index 7502664a9628..180ef855cfda 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -39,6 +39,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IntArray;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.media.permission.INativePermissionController;
@@ -62,6 +63,8 @@ import java.util.stream.Collectors;
/** Responsible for synchronizing system server permission state to the native audioserver. */
public class AudioServerPermissionProvider {
+ static final String TAG = "AudioServerPermissionProvider";
+
static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE];
static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD,
@@ -219,10 +222,13 @@ public class AudioServerPermissionProvider {
public void setIsolatedServiceUid(int uid, int owningUid) {
synchronized (mLock) {
if (mHdsUid == uid) return;
- var packageNameSet = mPackageMap.get(owningUid);
- if (packageNameSet == null) return;
- var packageName = packageNameSet.iterator().next();
- onModifyPackageState(uid, packageName, /* isRemove= */ false);
+ var packageNameSet = mPackageMap.get(UserHandle.getAppId(owningUid));
+ if (packageNameSet != null) {
+ var packageName = packageNameSet.iterator().next();
+ onModifyPackageState(uid, packageName, /* isRemove= */ false);
+ } else {
+ Log.wtf(TAG, "setIsolatedService owning uid not found");
+ }
// permissions
mHdsUid = uid;
if (mDest == null) {
@@ -249,11 +255,19 @@ public class AudioServerPermissionProvider {
public void clearIsolatedServiceUid(int uid) {
synchronized (mLock) {
- if (mHdsUid != uid) return;
- var packageNameSet = mPackageMap.get(uid);
- if (packageNameSet == null) return;
- var packageName = packageNameSet.iterator().next();
- onModifyPackageState(uid, packageName, /* isRemove= */ true);
+ var packageNameSet = mPackageMap.get(UserHandle.getAppId(uid));
+ if (mHdsUid != uid) {
+ Log.wtf(TAG,
+ "Unexpected isolated service uid cleared: " + uid + packageNameSet
+ + ", expected " + mHdsUid);
+ return;
+ }
+ if (packageNameSet != null) {
+ var packageName = packageNameSet.iterator().next();
+ onModifyPackageState(uid, packageName, /* isRemove= */ true);
+ } else {
+ Log.wtf(TAG, "clearIsolatedService uid not found");
+ }
// permissions
if (mDest == null) {
mIsUpdateDeferred = true;
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
index 9118c46e3b22..574e484edd16 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
@@ -167,7 +167,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction {
private boolean handleReportPowerStatus(int powerStatus) {
switch (powerStatus) {
case HdmiControlManager.POWER_STATUS_ON:
- selectDevice();
+ if (tv().getActiveSource().physicalAddress == mTarget.getPhysicalAddress()) {
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ } else {
+ selectDevice();
+ }
return true;
case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
if (mPowerStatusCounter < 4) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index d9db178e0dc2..6e6d00d62819 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1230,7 +1230,7 @@ public class InputManagerService extends IInputManager.Stub
"registerTabletModeChangedListener()")) {
throw new SecurityException("Requires TABLET_MODE_LISTENER permission");
}
- Objects.requireNonNull(listener, "event must not be null");
+ Objects.requireNonNull(listener, "listener must not be null");
synchronized (mTabletModeLock) {
final int callingPid = Binder.getCallingPid();
@@ -1342,7 +1342,7 @@ public class InputManagerService extends IInputManager.Stub
@Override
public void requestPointerCapture(IBinder inputChannelToken, boolean enabled) {
- Objects.requireNonNull(inputChannelToken, "event must not be null");
+ Objects.requireNonNull(inputChannelToken, "inputChannelToken must not be null");
mNative.requestPointerCapture(inputChannelToken, enabled);
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 6db62c8397f3..ccb9e3ea5cbe 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -301,7 +301,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
throw new IllegalArgumentException(
"Unknown session ID in closeSession: id=" + sessionId);
}
- halCloseEndpointSession(sessionId, ContextHubServiceUtil.toHalReason(reason));
+ mEndpointManager.halCloseEndpointSession(
+ sessionId, ContextHubServiceUtil.toHalReason(reason));
}
@Override
@@ -312,7 +313,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
// Iterate in reverse since cleanupSessionResources will remove the entry
for (int i = mSessionMap.size() - 1; i >= 0; i--) {
int id = mSessionMap.keyAt(i);
- halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
+ mEndpointManager.halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
cleanupSessionResources(id);
}
}
@@ -444,7 +445,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
int id = mSessionMap.keyAt(i);
HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo();
if (!hasEndpointPermissions(target)) {
- halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED);
+ mEndpointManager.halCloseEndpointSessionNoThrow(
+ id, Reason.PERMISSION_DENIED);
onCloseEndpointSession(id, Reason.PERMISSION_DENIED);
// Resource cleanup is done in onCloseEndpointSession
}
@@ -503,17 +505,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
}
- /* package */ void onEndpointSessionOpenRequest(
- int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
- Optional<Byte> error =
- onEndpointSessionOpenRequestInternal(sessionId, initiator, serviceDescriptor);
- if (error.isPresent()) {
- halCloseEndpointSessionNoThrow(sessionId, error.get());
- onCloseEndpointSession(sessionId, error.get());
- // Resource cleanup is done in onCloseEndpointSession
- }
- }
-
+ /** Handle close endpoint callback to the client side */
/* package */ void onCloseEndpointSession(int sessionId, byte reason) {
if (!cleanupSessionResources(sessionId)) {
Log.w(TAG, "Unknown session ID in onCloseEndpointSession: id=" + sessionId);
@@ -585,7 +577,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
}
- private Optional<Byte> onEndpointSessionOpenRequestInternal(
+ /* package */ Optional<Byte> onEndpointSessionOpenRequest(
int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
if (!hasEndpointPermissions(initiator)) {
Log.e(
@@ -594,15 +586,41 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ initiator
+ " doesn't have permission for "
+ mEndpointInfo);
- return Optional.of(Reason.PERMISSION_DENIED);
+ byte reason = Reason.PERMISSION_DENIED;
+ onCloseEndpointSession(sessionId, reason);
+ return Optional.of(reason);
}
+ // Check & handle error cases for duplicated session id.
+ final boolean existingSession;
+ final boolean existingSessionActive;
synchronized (mOpenSessionLock) {
if (hasSessionId(sessionId)) {
- Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId);
- return Optional.of(Reason.UNSPECIFIED);
+ existingSession = true;
+ existingSessionActive = mSessionMap.get(sessionId).isActive();
+ Log.w(
+ TAG,
+ "onEndpointSessionOpenRequest: "
+ + "Existing session ID: "
+ + sessionId
+ + ", isActive: "
+ + existingSessionActive);
+ } else {
+ existingSession = false;
+ existingSessionActive = false;
+ mSessionMap.put(sessionId, new Session(initiator, true));
+ }
+ }
+
+ if (existingSession) {
+ if (existingSessionActive) {
+ // Existing session is already active, call onSessionOpenComplete.
+ openSessionRequestComplete(sessionId);
+ return Optional.empty();
}
- mSessionMap.put(sessionId, new Session(initiator, true));
+ // Reject the session open request for now. Consider invalidating previous pending
+ // session open request based on timeout.
+ return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
}
boolean success =
@@ -610,7 +628,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
(consumer) ->
consumer.onSessionOpenRequest(
sessionId, initiator, serviceDescriptor));
- return success ? Optional.empty() : Optional.of(Reason.UNSPECIFIED);
+ byte reason = Reason.UNSPECIFIED;
+ if (!success) {
+ onCloseEndpointSession(sessionId, reason);
+ }
+ return success ? Optional.empty() : Optional.of(reason);
}
private byte onMessageReceivedInternal(int sessionId, HubMessage message) {
@@ -657,29 +679,6 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
/**
- * Calls the HAL closeEndpointSession API.
- *
- * @param sessionId The session ID to close
- * @param halReason The HAL reason
- */
- private void halCloseEndpointSession(int sessionId, byte halReason) throws RemoteException {
- try {
- mHubInterface.closeEndpointSession(sessionId, halReason);
- } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
- throw e;
- }
- }
-
- /** Same as halCloseEndpointSession but does not throw the exception */
- private void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) {
- try {
- halCloseEndpointSession(sessionId, halReason);
- } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
- Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
- }
- }
-
- /**
* Cleans up resources related to a session with the provided ID.
*
* @param sessionId The session ID to clean up resources for
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index 8ab581e1fb7a..e1561599517d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -29,6 +29,7 @@ import android.hardware.contexthub.IContextHubEndpoint;
import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.contexthub.IEndpointCommunication;
import android.hardware.contexthub.MessageDeliveryStatus;
+import android.hardware.contexthub.Reason;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
@@ -42,6 +43,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -316,6 +318,11 @@ import java.util.function.Consumer;
}
}
+ /** Returns if a sessionId can be allocated for the service hub. */
+ private boolean isSessionIdAllocatedForService(int sessionId) {
+ return sessionId > mMaxSessionId || sessionId < mMinSessionId;
+ }
+
/**
* Unregisters an endpoint given its ID.
*
@@ -337,8 +344,7 @@ import java.util.function.Consumer;
}
}
- @Override
- public void onEndpointSessionOpenRequest(
+ private Optional<Byte> onEndpointSessionOpenRequestInternal(
int sessionId,
HubEndpointInfo.HubEndpointIdentifier destination,
HubEndpointInfo.HubEndpointIdentifier initiator,
@@ -348,7 +354,7 @@ import java.util.function.Consumer;
TAG,
"onEndpointSessionOpenRequest: invalid destination hub ID: "
+ destination.getHub());
- return;
+ return Optional.of(Reason.ENDPOINT_INVALID);
}
ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint());
if (broker == null) {
@@ -356,7 +362,7 @@ import java.util.function.Consumer;
TAG,
"onEndpointSessionOpenRequest: unknown destination endpoint ID: "
+ destination.getEndpoint());
- return;
+ return Optional.of(Reason.ENDPOINT_INVALID);
}
HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator);
if (initiatorInfo == null) {
@@ -364,9 +370,29 @@ import java.util.function.Consumer;
TAG,
"onEndpointSessionOpenRequest: unknown initiator endpoint ID: "
+ initiator.getEndpoint());
- return;
+ return Optional.of(Reason.ENDPOINT_INVALID);
+ }
+ if (!isSessionIdAllocatedForService(sessionId)) {
+ Log.e(
+ TAG,
+ "onEndpointSessionOpenRequest: invalid session ID, rejected:"
+ + " sessionId="
+ + sessionId);
+ return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
}
- broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+ return broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+ }
+
+ @Override
+ public void onEndpointSessionOpenRequest(
+ int sessionId,
+ HubEndpointInfo.HubEndpointIdentifier destination,
+ HubEndpointInfo.HubEndpointIdentifier initiator,
+ String serviceDescriptor) {
+ Optional<Byte> errorOptional =
+ onEndpointSessionOpenRequestInternal(
+ sessionId, destination, initiator, serviceDescriptor);
+ errorOptional.ifPresent((error) -> halCloseEndpointSessionNoThrow(sessionId, error));
}
@Override
@@ -418,6 +444,30 @@ import java.util.function.Consumer;
}
}
+ /**
+ * Calls the HAL closeEndpointSession API.
+ *
+ * @param sessionId The session ID to close
+ * @param halReason The HAL reason
+ */
+ /* package */ void halCloseEndpointSession(int sessionId, byte halReason)
+ throws RemoteException {
+ try {
+ mHubInterface.closeEndpointSession(sessionId, halReason);
+ } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+ throw e;
+ }
+ }
+
+ /** Same as halCloseEndpointSession but does not throw the exception */
+ /* package */ void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) {
+ try {
+ halCloseEndpointSession(sessionId, halReason);
+ } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+ Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
+ }
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 7b4c56334868..7fd400ec8ac9 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -518,7 +518,6 @@ public class GnssNative {
if (!Flags.gnssAssistanceInterfaceJni()) {
return;
}
- Preconditions.checkState(!mRegistered);
Preconditions.checkState(mGnssAssistanceCallbacks == null);
mGnssAssistanceCallbacks = Objects.requireNonNull(callbacks);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8948bd196789..78554bdcf6aa 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6173,10 +6173,15 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public Map<String, AutomaticZenRule> getAutomaticZenRules() {
+ public ParceledListSlice getAutomaticZenRules() {
int callingUid = Binder.getCallingUid();
enforcePolicyAccess(callingUid, "getAutomaticZenRules");
- return mZenModeHelper.getAutomaticZenRules(getCallingZenUser(), callingUid);
+ List<AutomaticZenRule.AzrWithId> ruleList = new ArrayList<>();
+ for (Map.Entry<String, AutomaticZenRule> rule : mZenModeHelper.getAutomaticZenRules(
+ getCallingZenUser(), callingUid).entrySet()) {
+ ruleList.add(new AutomaticZenRule.AzrWithId(rule.getKey(), rule.getValue()));
+ }
+ return new ParceledListSlice<>(ruleList);
}
@Override
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index 6872ca9e46ee..f5daa8036726 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -59,6 +59,7 @@ import com.android.server.security.advancedprotection.features.DisallowCellular2
import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook;
import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook;
import com.android.server.security.advancedprotection.features.UsbDataAdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.DisallowWepAdvancedProtectionProvider;
import java.io.File;
import java.io.FileDescriptor;
@@ -131,7 +132,9 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
} catch (Exception e) {
Slog.e(TAG, "Failed to initialize UsbDataAdvancedProtection", e);
}
- }
+ }
+
+ mProviders.add(new DisallowWepAdvancedProtectionProvider());
}
// Only for tests
@@ -303,7 +306,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
getAdvancedProtectionFeatures_enforcePermission();
List<AdvancedProtectionFeature> features = new ArrayList<>();
for (int i = 0; i < mProviders.size(); i++) {
- features.addAll(mProviders.get(i).getFeatures());
+ features.addAll(mProviders.get(i).getFeatures(mContext));
}
for (int i = 0; i < mHooks.size(); i++) {
@@ -341,7 +344,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
writer.println(" Providers: ");
mProviders.stream().forEach(provider -> {
writer.println(" " + provider.getClass().getSimpleName());
- provider.getFeatures().stream().forEach(feature -> {
+ provider.getFeatures(mContext).stream().forEach(feature -> {
writer.println(" " + feature.getClass().getSimpleName());
});
});
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
index ed451f1e2257..6498cfc97aac 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
@@ -16,6 +16,8 @@
package com.android.server.security.advancedprotection.features;
+import android.annotation.NonNull;
+import android.content.Context;
import android.security.advancedprotection.AdvancedProtectionFeature;
import java.util.List;
@@ -23,5 +25,5 @@ import java.util.List;
/** @hide */
public abstract class AdvancedProtectionProvider {
/** The list of features provided */
- public abstract List<AdvancedProtectionFeature> getFeatures();
+ public abstract List<AdvancedProtectionFeature> getFeatures(@NonNull Context context);
}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java
new file mode 100644
index 000000000000..1505f68b699e
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java
@@ -0,0 +1,31 @@
+/*
+ * 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.server.security.advancedprotection.features;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+
+import java.util.List;
+
+public class DisallowWepAdvancedProtectionProvider extends AdvancedProtectionProvider {
+ public List<AdvancedProtectionFeature> getFeatures(@NonNull Context context) {
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ return wifiManager.getAvailableAdvancedProtectionFeatures();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
index 959609309da1..cd6a01478eef 100644
--- a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
@@ -156,6 +156,8 @@ class AppCompatSafeRegionPolicy {
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
if (mNeedsSafeRegionBounds) {
pw.println(prefix + " mNeedsSafeRegionBounds=true");
+ pw.println(
+ prefix + " latestSafeRegionBoundsOnActivity=" + getLatestSafeRegionBounds());
}
if (isLetterboxedForSafeRegionOnlyAllowed()) {
pw.println(prefix + " isLetterboxForSafeRegionOnlyAllowed=true");
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 2798e843d6dd..ab87459da01a 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -218,6 +218,11 @@ class Dimmer {
*/
protected void adjustAppearance(@NonNull WindowState dimmingContainer,
float alpha, int blurRadius) {
+ if (!mHost.isVisibleRequested()) {
+ // If the host is already going away, there is no point in keeping dimming
+ return;
+ }
+
if (mDimState != null || (alpha != 0 || blurRadius != 0)) {
final DimState d = obtainDimState(dimmingContainer);
d.prepareLookChange(alpha, blurRadius);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index bdd13722aba4..d356128205df 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -337,6 +337,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25;
public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
+ private static final int ACTIVITY_STATE_VISIBLE =
+ com.android.window.flags.Flags.useVisibleRequestedForProcessTracker()
+ ? ACTIVITY_STATE_FLAG_IS_VISIBLE
+ : ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE;
+
/**
* The state for oom-adjustment calculation. The higher 16 bits are the activity states, and the
* lower 16 bits are the task layer rank (see {@link Task#mLayerRank}). This field is written by
@@ -1260,8 +1265,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
int nonOccludedRatio = 0;
long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE;
final boolean wasResumed = hasResumedActivity();
- final boolean wasAnyVisible = (mActivityStateFlags
- & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
+ final boolean wasAnyVisible = (mActivityStateFlags & ACTIVITY_STATE_VISIBLE) != 0;
for (int i = mActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = mActivities.get(i);
if (r.isVisible()) {
@@ -1275,8 +1279,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
if (task.mLayerRank != Task.LAYER_RANK_INVISIBLE) {
stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK;
}
+ final ActivityRecord.State state = r.getState();
if (r.isVisibleRequested()) {
- if (r.isState(RESUMED)) {
+ if (state == RESUMED) {
stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
final int windowingMode = r.getWindowingMode();
if (windowingMode == WINDOWING_MODE_MULTI_WINDOW
@@ -1301,13 +1306,21 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// this process, we'd find out the one with the minimal layer, thus it'll
// get a higher adj score.
} else if (!visible && bestInvisibleState != PAUSING) {
- if (r.isState(PAUSING, PAUSED)) {
+ if (state == PAUSING) {
bestInvisibleState = PAUSING;
- } else if (r.isState(STOPPING)) {
+ // Treat PAUSING as visible in case the next activity in the same process has
+ // not yet been set as visible-requested.
+ if (com.android.window.flags.Flags.useVisibleRequestedForProcessTracker()
+ && r.isVisible()) {
+ stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE;
+ }
+ } else if (state == PAUSED) {
+ bestInvisibleState = PAUSED;
+ } else if (state == STOPPING) {
bestInvisibleState = STOPPING;
// Not "finishing" if any of activity isn't finishing.
allStoppingFinishing &= r.finishing;
- } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) {
+ } else if (bestInvisibleState == DESTROYED && state == STOPPED) {
if (task.mIsPerceptible) {
perceptibleTaskStoppedTimeMillis =
Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis);
@@ -1340,7 +1353,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
if (visible) {
stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE;
- } else if (bestInvisibleState == PAUSING) {
+ } else if (bestInvisibleState == PAUSING || bestInvisibleState == PAUSED) {
stateFlags |= ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED;
} else if (bestInvisibleState == STOPPING) {
stateFlags |= ACTIVITY_STATE_FLAG_IS_STOPPING;
@@ -1351,8 +1364,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mActivityStateFlags = stateFlags;
mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis;
- final boolean anyVisible = (stateFlags
- & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
+ final boolean anyVisible = (stateFlags & ACTIVITY_STATE_VISIBLE) != 0;
if (!wasAnyVisible && anyVisible) {
mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this);
mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index f6b107b60d62..d60807c7b001 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -62,6 +62,8 @@ public class PrepareGetRequestSession extends GetRequestSession {
Collectors.toSet())).size(); // Dedupe type strings
mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
mPrepareGetCredentialCallback = prepareGetCredentialCallback;
+
+ Slog.i(TAG, "PrepareGetRequestSession constructed.");
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 51ed6bb2aa40..f055febca3d5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -276,6 +276,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
import static com.android.server.devicepolicy.DevicePolicyEngine.DEFAULT_POLICY_SIZE_LIMIT;
+import static com.android.server.devicepolicy.DevicePolicyEngine.SYSTEM_SUPERVISION_ROLE;
import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE;
import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__COPE;
import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__DEVICE_OWNER;
@@ -16296,6 +16297,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return null;
}
+ /**
+ * When multiple admins enforce a policy, this method returns an admin according to this order:
+ * 1. Supervision
+ * 2. DPC
+ *
+ * Otherwise, it returns any other admin.
+ */
private android.app.admin.EnforcingAdmin getEnforcingAdminInternal(int userId,
String identifier) {
Objects.requireNonNull(identifier);
@@ -16304,16 +16312,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (admins.isEmpty()) {
return null;
}
-
- final EnforcingAdmin admin;
if (admins.size() == 1) {
- admin = admins.iterator().next();
- } else {
- Optional<EnforcingAdmin> dpc = admins.stream()
- .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst();
- admin = dpc.orElseGet(() -> admins.stream().findFirst().get());
+ return admins.iterator().next().getParcelableAdmin();
+ }
+ Optional<EnforcingAdmin> supervision = admins.stream()
+ .filter(a -> a.hasAuthority(
+ EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE)))
+ .findFirst();
+ if (supervision.isPresent()) {
+ return supervision.get().getParcelableAdmin();
+ }
+ Optional<EnforcingAdmin> dpc = admins.stream()
+ .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst();
+ if (dpc.isPresent()) {
+ return dpc.get().getParcelableAdmin();
}
- return admin == null ? null : admin.getParcelableAdmin();
+ return admins.iterator().next().getParcelableAdmin();
}
private <V> Set<EnforcingAdmin> getEnforcingAdminsForIdentifier(int userId, String identifier) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 543e32fae55f..9ff6eb66064f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -34,6 +34,7 @@ import android.app.admin.PackagePolicyKey;
import android.app.admin.PolicyKey;
import android.app.admin.PolicyValue;
import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
@@ -282,7 +283,9 @@ final class PolicyDefinition<V> {
static PolicyDefinition<Set<String>> PERMITTED_INPUT_METHODS = new PolicyDefinition<>(
new NoArgsPolicyKey(DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY),
- new MostRecent<>(),
+ (Flags.usePolicyIntersectionForPermittedInputMethods()
+ ? new StringSetIntersection()
+ : new MostRecent<>()),
POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
PolicyEnforcerCallbacks::noOp,
new PackageSetPolicySerializer());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java
new file mode 100644
index 000000000000..bc075b02b141
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java
@@ -0,0 +1,59 @@
+/*
+ * 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.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.app.admin.PolicyValue;
+import android.app.admin.PackageSetPolicyValue;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Objects;
+import java.util.Set;
+
+final class StringSetIntersection extends ResolutionMechanism<Set<String>> {
+
+ @Override
+ PolicyValue<Set<String>> resolve(
+ @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Set<String>>> adminPolicies) {
+ Objects.requireNonNull(adminPolicies);
+ Set<String> intersectionOfPolicies = null;
+ for (PolicyValue<Set<String>> policy : adminPolicies.values()) {
+ if (intersectionOfPolicies == null) {
+ intersectionOfPolicies = new HashSet<>(policy.getValue());
+ } else {
+ intersectionOfPolicies.retainAll(policy.getValue());
+ }
+ }
+ if (intersectionOfPolicies == null) {
+ return null;
+ }
+ // Note that the resulting set below may be empty, but that's fine:
+ // particular policy should decide what is the meaning of an empty set.
+ return new PackageSetPolicyValue(intersectionOfPolicies);
+ }
+
+ @Override
+ android.app.admin.StringSetIntersection getParcelableResolutionMechanism() {
+ return new android.app.admin.StringSetIntersection();
+ }
+
+ @Override
+ public String toString() {
+ return "StringSetIntersection {}";
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index a4c71bd6094e..2227eebdb128 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -251,6 +251,35 @@ public class DeviceSelectActionFromTvTest {
}
@Test
+ public void testDeviceSelect_DeviceAssertsActiveSource_singleSetStreamPathMessage() {
+ // TV was watching playback2 device connected at port 2, and wants to select
+ // playback1.
+ TestActionTimer actionTimer = new TestActionTimer();
+ TestCallback callback = new TestCallback();
+ DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
+ /*isCec20=*/false);
+ mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
+ "testDeviceSelect");
+ action.start();
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
+ mNativeWrapper.clearResultMessages();
+ mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1,
+ "testDeviceSelect");
+ mTestLooper.dispatchAll();
+
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
+ action.processCommand(REPORT_POWER_STATUS_ON);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH);
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
+ @Test
public void testDeviceSelect_DeviceInStandbyStatus_Cec14b() {
mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
"testDeviceSelect");
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 4d2dcf65bfeb..43b1ec393bf6 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -67,12 +68,15 @@ public class ContextHubEndpointTest {
private static final int SESSION_ID_RANGE = ContextHubEndpointManager.SERVICE_SESSION_RANGE;
private static final int MIN_SESSION_ID = 0;
private static final int MAX_SESSION_ID = MIN_SESSION_ID + SESSION_ID_RANGE - 1;
+ private static final int SESSION_ID_FOR_OPEN_REQUEST = MAX_SESSION_ID + 1;
+ private static final int INVALID_SESSION_ID_FOR_OPEN_REQUEST = MIN_SESSION_ID + 1;
private static final String ENDPOINT_NAME = "Example test endpoint";
private static final int ENDPOINT_ID = 1;
private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub";
private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
+ private static final String ENDPOINT_SERVICE_DESCRIPTOR = "serviceDescriptor";
private static final int TARGET_ENDPOINT_ID = 1;
private static final int SAMPLE_MESSAGE_TYPE = 1234;
@@ -225,6 +229,105 @@ public class ContextHubEndpointTest {
}
@Test
+ public void testEndpointSessionOpenRequest() throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+ mEndpointManager.onEndpointSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST,
+ endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+ targetInfo.getIdentifier(),
+ ENDPOINT_SERVICE_DESCRIPTOR);
+
+ verify(mMockCallback)
+ .onSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR);
+
+ // Accept
+ endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
+ verify(mMockEndpointCommunications)
+ .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
+ public void testEndpointSessionOpenRequestWithInvalidSessionId() throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+ mEndpointManager.onEndpointSessionOpenRequest(
+ INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+ endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+ targetInfo.getIdentifier(),
+ ENDPOINT_SERVICE_DESCRIPTOR);
+ verify(mMockEndpointCommunications)
+ .closeEndpointSession(
+ INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+ Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+ verify(mMockCallback, never())
+ .onSessionOpenRequest(
+ INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+ targetInfo,
+ ENDPOINT_SERVICE_DESCRIPTOR);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
+ public void testEndpointSessionOpenRequest_duplicatedSessionId_noopWhenSessionIsActive()
+ throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+ mEndpointManager.onEndpointSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST,
+ endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+ targetInfo.getIdentifier(),
+ ENDPOINT_SERVICE_DESCRIPTOR);
+ endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
+ // Now session with id SESSION_ID_FOR_OPEN_REQUEST is active
+
+ // Duplicated session open request
+ mEndpointManager.onEndpointSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST,
+ endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+ targetInfo.getIdentifier(),
+ ENDPOINT_SERVICE_DESCRIPTOR);
+
+ // Client API is only invoked once
+ verify(mMockCallback, times(1))
+ .onSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR);
+ // HAL still receives two open complete notifications
+ verify(mMockEndpointCommunications, times(2))
+ .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
public void testMessageTransaction() throws RemoteException {
IContextHubEndpoint endpoint = registerExampleEndpoint();
testMessageTransactionInternal(endpoint, /* deliverMessageStatus= */ true);
diff --git a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
index c7a06b8eec7b..339bac4f768b 100644
--- a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
@@ -259,7 +259,7 @@ public class AdvancedProtectionServiceTest {
AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
@Override
- public List<AdvancedProtectionFeature> getFeatures() {
+ public List<AdvancedProtectionFeature> getFeatures(Context context) {
return List.of(feature2);
}
};
@@ -291,7 +291,7 @@ public class AdvancedProtectionServiceTest {
AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
@Override
- public List<AdvancedProtectionFeature> getFeatures() {
+ public List<AdvancedProtectionFeature> getFeatures(Context context) {
return List.of(feature2);
}
};
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index bc8b7becc919..902171d614d9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16844,22 +16844,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void updateAutomaticZenRule_implicitRuleWithoutCPS_disallowedFromApp() throws Exception {
setUpRealZenTest();
mService.setCallerIsNormalPackage();
- assertThat(mBinderService.getAutomaticZenRules()).isEmpty();
+ assertThat(mBinderService.getAutomaticZenRules().getList()).isEmpty();
// Create an implicit zen rule by calling setNotificationPolicy from an app.
mBinderService.setNotificationPolicy(mPkg, new NotificationManager.Policy(0, 0, 0), false);
- assertThat(mBinderService.getAutomaticZenRules()).hasSize(1);
- Map.Entry<String, AutomaticZenRule> rule = getOnlyElement(
- mBinderService.getAutomaticZenRules().entrySet());
- assertThat(rule.getValue().getOwner()).isNull();
- assertThat(rule.getValue().getConfigurationActivity()).isNull();
+ assertThat(mBinderService.getAutomaticZenRules().getList()).hasSize(1);
+ AutomaticZenRule.AzrWithId rule = getOnlyElement(
+ (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList());
+ assertThat(rule.mRule.getOwner()).isNull();
+ assertThat(rule.mRule.getConfigurationActivity()).isNull();
// Now try to update said rule (e.g. disable it). Should fail.
// We also validate the exception message because NPE could be thrown by all sorts of test
// issues (e.g. misconfigured mocks).
- rule.getValue().setEnabled(false);
+ rule.mRule.setEnabled(false);
NullPointerException e = assertThrows(NullPointerException.class,
- () -> mBinderService.updateAutomaticZenRule(rule.getKey(), rule.getValue(), false));
+ () -> mBinderService.updateAutomaticZenRule(rule.mId, rule.mRule, false));
assertThat(e.getMessage()).isEqualTo(
"Rule must have a ConditionProviderService and/or configuration activity");
}
@@ -16869,24 +16869,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void updateAutomaticZenRule_implicitRuleWithoutCPS_allowedFromSystem() throws Exception {
setUpRealZenTest();
mService.setCallerIsNormalPackage();
- assertThat(mBinderService.getAutomaticZenRules()).isEmpty();
+ assertThat(mBinderService.getAutomaticZenRules().getList()).isEmpty();
// Create an implicit zen rule by calling setNotificationPolicy from an app.
mBinderService.setNotificationPolicy(mPkg, new NotificationManager.Policy(0, 0, 0), false);
- assertThat(mBinderService.getAutomaticZenRules()).hasSize(1);
- Map.Entry<String, AutomaticZenRule> rule = getOnlyElement(
- mBinderService.getAutomaticZenRules().entrySet());
- assertThat(rule.getValue().getOwner()).isNull();
- assertThat(rule.getValue().getConfigurationActivity()).isNull();
+ assertThat(mBinderService.getAutomaticZenRules().getList()).hasSize(1);
+ AutomaticZenRule.AzrWithId rule = getOnlyElement(
+ (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList());
+ assertThat(rule.mRule.getOwner()).isNull();
+ assertThat(rule.mRule.getConfigurationActivity()).isNull();
// Now update said rule from Settings (e.g. disable it). Should work!
mService.isSystemUid = true;
- rule.getValue().setEnabled(false);
- mBinderService.updateAutomaticZenRule(rule.getKey(), rule.getValue(), false);
+ rule.mRule.setEnabled(false);
+ mBinderService.updateAutomaticZenRule(rule.mId, rule.mRule, false);
- Map.Entry<String, AutomaticZenRule> updatedRule = getOnlyElement(
- mBinderService.getAutomaticZenRules().entrySet());
- assertThat(updatedRule.getValue().isEnabled()).isFalse();
+ AutomaticZenRule.AzrWithId updatedRule = getOnlyElement(
+ (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList());
+ assertThat(updatedRule.mRule.isEnabled()).isFalse();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 9dc70266bf3d..5347f9a36652 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -382,12 +382,17 @@ public class WindowProcessControllerTests extends WindowTestsBase {
assertFalse(tracker.hasResumedActivity(mWpc.mUid));
assertTrue(mWpc.hasForegroundActivities());
- activity.setVisibility(false);
activity.setVisibleRequested(false);
- activity.setState(STOPPED, "test");
-
+ if (com.android.window.flags.Flags.useVisibleRequestedForProcessTracker()) {
+ assertTrue("PAUSING is visible", mWpc.hasVisibleActivities());
+ activity.setState(PAUSED, "test");
+ } else {
+ activity.setVisible(false);
+ }
verify(tracker).onAllActivitiesInvisible(mWpc);
assertFalse(mWpc.hasVisibleActivities());
+
+ activity.setState(STOPPED, "test");
assertFalse(mWpc.hasForegroundActivities());
}
diff --git a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
index d9bb7db17685..5419d9444abd 100644
--- a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
+++ b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
@@ -110,7 +110,7 @@ public class TextToSpeechTests extends InstrumentationTestCase {
blockingCallSpeak("foo bar", delegate);
ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class);
Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(),
- Mockito.<SynthesisCallback>anyObject());
+ Mockito.<SynthesisCallback>any());
assertEquals("eng", req.getValue().getLanguage());
assertEquals("USA", req.getValue().getCountry());
@@ -133,7 +133,7 @@ public class TextToSpeechTests extends InstrumentationTestCase {
blockingCallSpeak("le fou barre", delegate);
ArgumentCaptor<SynthesisRequest> req2 = ArgumentCaptor.forClass(SynthesisRequest.class);
Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req2.capture(),
- Mockito.<SynthesisCallback>anyObject());
+ Mockito.<SynthesisCallback>any());
// The params are basically unchanged.
assertEquals("eng", req2.getValue().getLanguage());
@@ -177,7 +177,7 @@ public class TextToSpeechTests extends InstrumentationTestCase {
blockingCallSpeak("foo bar", delegate);
ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class);
Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(),
- Mockito.<SynthesisCallback>anyObject());
+ Mockito.<SynthesisCallback>any());
assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage());
assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry());
@@ -189,8 +189,8 @@ public class TextToSpeechTests extends InstrumentationTestCase {
private void blockingCallSpeak(String speech, IDelegate mock) throws
InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
- doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>anyObject(),
- Mockito.<SynthesisCallback>anyObject());
+ doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>any(),
+ Mockito.<SynthesisCallback>any());
mTts.speak(speech, TextToSpeech.QUEUE_ADD, null);
awaitCountDown(latch, 5, TimeUnit.SECONDS);