summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java6
-rw-r--r--cmds/idmap2/include/idmap2/Idmap.h4
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ContextImpl.java13
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl1
-rw-r--r--core/java/android/app/Notification.java100
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java3
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig2
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig8
-rw-r--r--core/java/android/content/pm/PackageManager.java4
-rw-r--r--core/java/android/content/res/AssetManager.java22
-rw-r--r--core/java/android/content/res/Configuration.java5
-rw-r--r--core/java/android/content/res/ResourcesImpl.java4
-rw-r--r--core/java/android/hardware/display/DisplayManager.java74
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java103
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java2
-rw-r--r--core/java/android/os/PerfettoTrackEventExtra.java21
-rw-r--r--core/java/android/os/vibrator/flags.aconfig10
-rw-r--r--core/java/android/provider/Settings.java10
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java1
-rw-r--r--core/java/android/text/Layout.java46
-rw-r--r--core/java/android/view/Display.java7
-rw-r--r--core/java/android/view/InsetsController.java6
-rw-r--r--core/java/android/view/NotificationHeaderView.java10
-rw-r--r--core/java/android/view/View.java17
-rw-r--r--core/java/android/view/ViewRootImpl.java82
-rw-r--r--core/java/android/view/ViewRootRectTracker.java21
-rw-r--r--core/java/android/view/WindowInsets.java3
-rw-r--r--core/java/android/window/DesktopModeFlags.java1
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig24
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter.java4
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java12
-rw-r--r--core/java/com/android/internal/os/PowerStats.java40
-rw-r--r--core/java/com/android/internal/pm/pkg/component/AconfigFlags.java7
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java4
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java2
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogCommandHandler.java4
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogConfigurationService.java6
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java27
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java54
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java8
-rw-r--r--core/java/com/android/internal/widget/MessagingLayout.java98
-rw-r--r--core/jni/android_app_PropertyInvalidatedCache.cpp6
-rw-r--r--core/jni/android_app_PropertyInvalidatedCache.h54
-rw-r--r--core/jni/android_os_PerfettoTrackEventExtra.cpp166
-rw-r--r--core/jni/android_util_AssetManager.cpp10
-rw-r--r--core/jni/android_view_SurfaceControl.cpp26
-rw-r--r--core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp8
-rw-r--r--core/proto/OWNERS1
-rw-r--r--core/res/res/drawable-w192dp/loader_horizontal_watch.xml97
-rw-r--r--core/res/res/drawable-w204dp/loader_horizontal_watch.xml104
-rw-r--r--core/res/res/drawable-w216dp/loader_horizontal_watch.xml105
-rw-r--r--core/res/res/drawable-w228dp/loader_horizontal_watch.xml103
-rw-r--r--core/res/res/drawable-w240dp/loader_horizontal_watch.xml104
-rw-r--r--core/res/res/drawable/loader_horizontal_watch.xml103
-rw-r--r--core/res/res/drawable/progress_ring_watch.xml24
-rw-r--r--core/res/res/layout/notification_2025_conversation_header.xml189
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_call.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_conversation.xml216
-rw-r--r--core/res/res/layout/notification_2025_template_conversation.xml159
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_call.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_conversation.xml75
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_messaging.xml6
-rw-r--r--core/res/res/layout/notification_2025_top_line_views.xml2
-rw-r--r--core/res/res/values-w192dp/dimens_watch.xml28
-rw-r--r--core/res/res/values-w216dp/dimens_watch.xml21
-rw-r--r--core/res/res/values-w228dp/dimens_watch.xml21
-rw-r--r--core/res/res/values-w240dp/dimens_material.xml4
-rw-r--r--core/res/res/values-watch/styles_device_defaults.xml3
-rw-r--r--core/res/res/values/attrs.xml8
-rw-r--r--core/res/res/values/config_telephony.xml7
-rw-r--r--core/res/res/values/dimens_watch.xml6
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java116
-rw-r--r--core/tests/coretests/src/android/os/PerfettoTraceTest.java85
-rw-r--r--core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java4
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java115
-rw-r--r--core/tests/coretests/src/android/view/ViewRootRectTrackerTest.java70
-rw-r--r--graphics/java/android/framework_graphics.aconfig8
-rw-r--r--graphics/java/android/graphics/drawable/GradientDrawable.java121
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt151
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt168
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt170
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt233
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt9
-rw-r--r--libs/androidfw/AssetManager2.cpp82
-rw-r--r--libs/androidfw/Idmap.cpp42
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h10
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h43
-rw-r--r--media/java/android/media/MediaRoute2Info.java6
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java19
-rw-r--r--native/android/surface_control.cpp28
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt1
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt8
-rw-r--r--packages/CtsShim/build/Android.bp30
-rw-r--r--packages/CtsShim/build/shim/AndroidManifest.xml8
-rw-r--r--packages/CtsShim/build/shim/AndroidManifestUpgrade.xml30
-rw-r--r--packages/SettingsLib/AndroidManifest.xml7
-rw-r--r--packages/SettingsLib/BannerMessagePreference/Android.bp1
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml157
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml137
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml8
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java222
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java16
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt196
-rw-r--r--packages/SettingsLib/ButtonPreference/Android.bp1
-rw-r--r--packages/SettingsLib/IllustrationPreference/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/IllustrationPreference/res/values/strings.xml27
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java93
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt6
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt4
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig24
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml8
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml8
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml8
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml8
-rw-r--r--packages/SettingsLib/res/layout/activity_create_new_user.xml (renamed from core/res/res/values-w204dp-round-watch/dimens_watch.xml)15
-rw-r--r--packages/SettingsLib/res/values/styles.xml9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java34
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java25
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java50
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeSchedules.java45
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java145
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java18
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java37
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java117
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java114
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java89
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt8
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt6
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt87
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt162
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt10
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewTransitionRegistryTest.kt71
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt88
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt139
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt176
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt48
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java)73
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt52
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt145
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java12
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java5
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/shared/Android.bp1
-rw-r--r--packages/SystemUI/shared/res/values/bools.xml5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/ailabs/OWNERS1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java125
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToAodTransitionViewModel.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/rotationlock/DeviceStateAutoRotateModule.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java164
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt18
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToAodTransitionViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt4
-rw-r--r--packages/Vcn/TEST_MAPPING8
-rw-r--r--packages/Vcn/framework-b/Android.bp3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java20
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java20
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java6
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java6
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationDiskStore.java42
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationStore.java12
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java21
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java5
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java4
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java40
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java5
-rw-r--r--services/core/java/com/android/server/am/OWNERS4
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java26
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java3
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java1
-rw-r--r--services/core/java/com/android/server/am/UserController.java2
-rw-r--r--services/core/java/com/android/server/am/UserSwitchingDialog.java6
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java16
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java18
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java8
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java3
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java27
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig28
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java3
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java5
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS1
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java25
-rw-r--r--services/core/java/com/android/server/policy/PermissionPolicyService.java22
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java56
-rw-r--r--services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java13
-rw-r--r--services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java18
-rw-r--r--services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java6
-rw-r--r--services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java14
-rw-r--r--services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java33
-rw-r--r--services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java28
-rw-r--r--services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java29
-rw-r--r--services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java20
-rw-r--r--services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java27
-rw-r--r--services/core/java/com/android/server/power/stats/processor/MultiStateStats.java5
-rw-r--r--services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java33
-rw-r--r--services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java17
-rw-r--r--services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java18
-rw-r--r--services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java19
-rw-r--r--services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java27
-rw-r--r--services/core/java/com/android/server/storage/StorageSessionController.java37
-rw-r--r--services/core/java/com/android/server/vibrator/AbstractVibratorStep.java2
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/ExternalVibrationSession.java2
-rw-r--r--services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java8
-rw-r--r--services/core/java/com/android/server/vibrator/SingleVibrationSession.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java35
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSession.java2
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java35
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorController.java65
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java212
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java100
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java32
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java9
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java128
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java2
-rw-r--r--services/core/java/com/android/server/wm/PresentationController.java10
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java38
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java35
-rw-r--r--services/core/java/com/android/server/wm/TrustedPresentationListenerController.java12
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java300
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java230
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java28
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java50
-rw-r--r--services/core/jni/Android.bp1
-rw-r--r--services/core/jni/com_android_server_vibrator_VibratorController.cpp56
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java9
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java78
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java107
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java102
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java4
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java5
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java10
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java2
-rw-r--r--services/tests/servicestests/Android.bp3
-rw-r--r--services/tests/servicestests/AndroidManifest.xml4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java210
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java21
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java10
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java293
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java61
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java60
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java106
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java15
-rw-r--r--startop/OWNERS1
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt45
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt130
-rw-r--r--tests/Input/Android.bp3
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt12
-rw-r--r--tests/SoundTriggerTestApp/OWNERS1
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java13
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java9
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java2
-rw-r--r--tools/aapt2/cmd/Link.cpp4
-rw-r--r--tools/aapt2/cmd/Link.h9
-rw-r--r--tools/aapt2/cmd/Link_test.cpp41
-rw-r--r--tools/aapt2/readme.md2
442 files changed, 9336 insertions, 4963 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index ebfda527001d..010006edc995 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -809,7 +809,11 @@ public final class JobServiceContext implements ServiceConnection {
if (!verifyCallerLocked(cb)) {
return;
}
-
+ if (mVerb != VERB_EXECUTING) {
+ // Any state other than executing means the
+ // job is in transient or stopped state
+ return;
+ }
executing = getRunningJobLocked();
}
if (executing != null && jobId == executing.getJobId()) {
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 1f15daf1ba47..335dc97bf964 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -178,8 +178,8 @@ class IdmapHeader {
};
struct IdmapConstraint {
- // Constraint type can be TYPE_DISPLAY_ID or TYP_DEVICE_ID, please refer
- // to ConstraintType in OverlayConstraint.java
+ // Constraint type can be android::kOverlayConstraintTypeDisplayId or
+ // android::kOverlayConstraintTypeDeviceId
uint32_t constraint_type;
uint32_t constraint_value;
diff --git a/core/api/current.txt b/core/api/current.txt
index f5dcf2de4c51..9ebb5068bf19 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18112,6 +18112,7 @@ package android.graphics.drawable {
method public void setThickness(@Px int);
method public void setThicknessRatio(@FloatRange(from=0.0f, fromInclusive=false) float);
method public void setUseLevel(boolean);
+ field @FlaggedApi("com.android.graphics.flags.gradient_drawable_shape_rounded_cap") public static final int ARC = 4; // 0x4
field public static final int LINE = 2; // 0x2
field public static final int LINEAR_GRADIENT = 0; // 0x0
field public static final int OVAL = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 89b377314887..4222c7c64672 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1737,6 +1737,7 @@ package android.hardware.display {
method @NonNull public int[] getUserDisabledHdrTypes();
method public boolean isMinimalPostProcessingRequested(int);
method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void overrideHdrTypes(int, @NonNull int[]);
+ method @FlaggedApi("com.android.server.display.feature.flags.delay_implicit_rr_registration_until_rr_accessed") public void resetImplicitRefreshRateCallbackStatus();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
method @RequiresPermission(android.Manifest.permission.MODIFY_HDR_CONVERSION_MODE) public void setHdrConversionMode(@NonNull android.hardware.display.HdrConversionMode);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0519695ff7fe..1a6e9b07067f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2976,6 +2976,13 @@ class ContextImpl extends Context {
if (display != null) {
updateDeviceIdIfChanged(display.getDisplayId());
}
+ updateResourceOverlayConstraints();
+ }
+
+ private void updateResourceOverlayConstraints() {
+ if (mResources != null) {
+ mResources.getAssets().setOverlayConstraints(getDisplayId(), getDeviceId());
+ }
}
@Override
@@ -2988,9 +2995,11 @@ class ContextImpl extends Context {
}
}
- return new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+ final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), mSplitName,
mToken, mUser, mFlags, mClassLoader, null, deviceId, true);
+ context.updateResourceOverlayConstraints();
+ return context;
}
@NonNull
@@ -3285,6 +3294,7 @@ class ContextImpl extends Context {
mDeviceId = updatedDeviceId;
mAttributionSource = createAttributionSourceWithDeviceId(mAttributionSource, mDeviceId);
notifyOnDeviceChangedListeners(updatedDeviceId);
+ updateResourceOverlayConstraints();
}
}
@@ -3700,6 +3710,7 @@ class ContextImpl extends Context {
mResourcesManager.setLocaleConfig(lc);
}
}
+ updateResourceOverlayConstraints();
}
void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 4b1afa517122..01b2953362b5 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -146,6 +146,7 @@ interface IActivityTaskManager {
int getFrontActivityScreenCompatMode();
void setFrontActivityScreenCompatMode(int mode);
void setFocusedTask(int taskId);
+ boolean setTaskIsPerceptible(int taskId, boolean isPerceptible);
boolean removeTask(int taskId);
void removeAllVisibleRecentTasks();
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, boolean filterOnlyVisibleRecents,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5dca1c70a2e6..06047a42e70d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -18,6 +18,7 @@ package android.app;
import static android.annotation.Dimension.DP;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
+import static android.app.Flags.notificationsRedesignTemplates;
import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -818,7 +819,8 @@ public class Notification implements Parcelable
R.layout.notification_2025_template_expanded_base,
R.layout.notification_2025_template_heads_up_base,
R.layout.notification_2025_template_header,
- R.layout.notification_2025_template_conversation,
+ R.layout.notification_2025_template_collapsed_conversation,
+ R.layout.notification_2025_template_expanded_conversation,
R.layout.notification_2025_template_collapsed_call,
R.layout.notification_2025_template_expanded_call,
R.layout.notification_2025_template_collapsed_messaging,
@@ -5963,7 +5965,8 @@ public class Notification implements Parcelable
|| resId == getCompactHeadsUpBaseLayoutResource()
|| resId == getMessagingCompactHeadsUpLayoutResource()
|| resId == getCollapsedMessagingLayoutResource()
- || resId == getCollapsedMediaLayoutResource());
+ || resId == getCollapsedMediaLayoutResource()
+ || resId == getCollapsedConversationLayoutResource());
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
@@ -6001,8 +6004,7 @@ public class Notification implements Parcelable
// Update margins to leave space for the top line (but not for headerless views like
// HUNS, which use a different layout that already accounts for that). Templates that
// have content that will be displayed under the small icon also use a different margin.
- if (Flags.notificationsRedesignTemplates()
- && !p.mHeaderless && !p.mSkipTopLineAlignment) {
+ if (Flags.notificationsRedesignTemplates() && !p.mHeaderless) {
int margin = getContentMarginTop(mContext,
R.dimen.notification_2025_content_margin_top);
contentView.setViewLayoutMargin(R.id.notification_main_column,
@@ -7673,12 +7675,18 @@ public class Notification implements Parcelable
}
}
+ // Note: In the 2025 redesign, we use two separate layouts for the collapsed and expanded
+ // version of conversations. See below.
private int getConversationLayoutResource() {
- if (Flags.notificationsRedesignTemplates()) {
- return R.layout.notification_2025_template_conversation;
- } else {
- return R.layout.notification_template_material_conversation;
- }
+ return R.layout.notification_template_material_conversation;
+ }
+
+ private int getCollapsedConversationLayoutResource() {
+ return R.layout.notification_2025_template_collapsed_conversation;
+ }
+
+ private int getExpandedConversationLayoutResource() {
+ return R.layout.notification_2025_template_expanded_conversation;
}
private int getCollapsedCallLayoutResource() {
@@ -9462,7 +9470,7 @@ public class Notification implements Parcelable
boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
- boolean isHeaderless = !isConversationLayout && isCollapsed;
+ boolean isLegacyHeaderless = !isConversationLayout && isCollapsed;
//TODO (b/217799515): ensure mConversationTitle always returns the correct
// conversationTitle, probably set mConversationTitle = conversationTitle after this
@@ -9483,7 +9491,8 @@ public class Notification implements Parcelable
} else {
isOneToOne = !isGroupConversation();
}
- if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
+ if ((isLegacyHeaderless || notificationsRedesignTemplates())
+ && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
conversationTitle = getOtherPersonName();
}
@@ -9493,22 +9502,24 @@ public class Notification implements Parcelable
.viewType(viewType)
.highlightExpander(isConversationLayout)
.hideProgress(true)
- .title(isHeaderless ? conversationTitle : null)
.text(null)
.hideLeftIcon(isOneToOne)
- .hideRightIcon(hideRightIcons || isOneToOne)
- .headerTextSecondary(isHeaderless ? null : conversationTitle)
- .skipTopLineAlignment(true);
+ .hideRightIcon(hideRightIcons || isOneToOne);
+ if (notificationsRedesignTemplates()) {
+ p.title(conversationTitle)
+ .hideAppName(isCollapsed);
+ } else {
+ p.title(isLegacyHeaderless ? conversationTitle : null)
+ .headerTextSecondary(isLegacyHeaderless ? null : conversationTitle);
+ }
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- isConversationLayout
- ? mBuilder.getConversationLayoutResource()
- : isCollapsed
- ? mBuilder.getCollapsedMessagingLayoutResource()
- : mBuilder.getExpandedMessagingLayoutResource(),
+ getMessagingLayoutResource(isConversationLayout, isCollapsed),
p,
bindResult);
- if (isConversationLayout) {
+ if (isConversationLayout && !notificationsRedesignTemplates()) {
+ // Redesign note: This view is replaced by the `title`, which is handled normally.
mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+ // Redesign note: This special divider is no longer needed.
mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
}
@@ -9538,7 +9549,18 @@ public class Notification implements Parcelable
contentView.setBoolean(R.id.status_bar_latest_event_content,
"setIsImportantConversation", isImportantConversation);
}
- if (isHeaderless) {
+ if (notificationsRedesignTemplates() && !isCollapsed) {
+ // Align the title to the app/small icon in the expanded form. In other layouts,
+ // this margin is added directly to the notification_main_column parent, but for
+ // messages we don't want the margin to be applied to the actual messaging
+ // content since it can contain icons that are displayed below the app icon.
+ Resources res = mBuilder.mContext.getResources();
+ int marginStart = res.getDimensionPixelSize(
+ R.dimen.notification_2025_content_margin_start);
+ contentView.setViewLayoutMargin(R.id.title,
+ RemoteViews.MARGIN_START, marginStart, TypedValue.COMPLEX_UNIT_PX);
+ }
+ if (isLegacyHeaderless) {
// Collapsed legacy messaging style has a 1-line limit.
contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
}
@@ -9549,6 +9571,33 @@ public class Notification implements Parcelable
return contentView;
}
+ private int getMessagingLayoutResource(boolean isConversationLayout, boolean isCollapsed) {
+ if (notificationsRedesignTemplates()) {
+ // Note: We eventually would like to use the same layouts for both conversations and
+ // regular messaging notifications.
+ if (isConversationLayout) {
+ if (isCollapsed) {
+ return mBuilder.getCollapsedConversationLayoutResource();
+ } else {
+ return mBuilder.getExpandedConversationLayoutResource();
+ }
+ } else {
+ if (isCollapsed) {
+ return mBuilder.getCollapsedMessagingLayoutResource();
+ } else {
+ return mBuilder.getExpandedMessagingLayoutResource();
+ }
+ }
+
+ } else {
+ return isConversationLayout
+ ? mBuilder.getConversationLayoutResource()
+ : isCollapsed
+ ? mBuilder.getCollapsedMessagingLayoutResource()
+ : mBuilder.getExpandedMessagingLayoutResource();
+ }
+ }
+
private CharSequence getKey(Person person) {
return person == null ? null
: person.getKey() == null ? person.getName() : person.getKey();
@@ -14676,7 +14725,6 @@ public class Notification implements Parcelable
Icon mPromotedPicture;
boolean mCallStyleActions;
boolean mAllowTextWithProgress;
- boolean mSkipTopLineAlignment;
int mTitleViewId;
int mTextViewId;
@Nullable CharSequence mTitle;
@@ -14702,7 +14750,6 @@ public class Notification implements Parcelable
mPromotedPicture = null;
mCallStyleActions = false;
mAllowTextWithProgress = false;
- mSkipTopLineAlignment = false;
mTitleViewId = R.id.title;
mTextViewId = R.id.text;
mTitle = null;
@@ -14769,11 +14816,6 @@ public class Notification implements Parcelable
return this;
}
- public StandardTemplateParams skipTopLineAlignment(boolean skipTopLineAlignment) {
- mSkipTopLineAlignment = skipTopLineAlignment;
- return this;
- }
-
final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
this.mHideSnoozeButton = hideSnoozeButton;
return this;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 08719fc549f8..500f7585b673 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -14200,6 +14200,9 @@ public class DevicePolicyManager {
* <li>Manifest.permission.ACTIVITY_RECOGNITION</li>
* <li>Manifest.permission.BODY_SENSORS</li>
* </ul>
+ * On devices running {@link android.os.Build.VERSION_CODES#BAKLAVA}, the
+ * {@link android.health.connect.HealthPermissions} are also included in the
+ * restricted list.
* <p>
* A profile owner may not grant these permissions (i.e. call this method with any of the
* permissions listed above and {@code grantState} of {@code #PERMISSION_GRANT_STATE_GRANTED}),
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index bc1f7cea7fce..1de034b23a7a 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -24,7 +24,7 @@ flag {
}
flag {
- name: "contextual_search_window_layer"
+ name: "contextual_search_prevent_self_capture"
namespace: "sysui_integrations"
description: "Identify live contextual search UI to exclude from contextual search screenshot."
bug: "390176823"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index ba1473cf5ed7..67ade79e1b94 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -135,3 +135,11 @@ flag {
description: "Show virtual devices in Settings"
bug: "338974320"
}
+
+flag {
+ name: "migrate_viewconfiguration_constants_to_resources"
+ namespace: "virtual_devices"
+ description: "Use resources instead of constants in ViewConfiguration"
+ is_fixed_read_only: true
+ bug: "370928384"
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f91b2474fdac..9e91f5944504 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2038,7 +2038,7 @@ public abstract class PackageManager {
public static final int INSTALL_SCENARIO_DEFAULT = 0;
/**
- * Installation scenario providing the fastest “install button to launch" experience possible.
+ * Installation scenario providing the fastest "install button to launch" experience possible.
*/
public static final int INSTALL_SCENARIO_FAST = 1;
@@ -3585,7 +3585,7 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device is
- * compatible with Android’s security model.
+ * compatible with Android's security model.
*
* <p>See sections 2 and 9 in the
* <a href="https://source.android.com/compatibility/android-cdd">Android CDD</a> for more
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7cd2d31ac974..bcb50881d327 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -148,8 +148,8 @@ public final class AssetManager implements AutoCloseable {
* @hide
*/
public static class Builder {
- private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();
- private ArrayList<ResourcesLoader> mLoaders = new ArrayList<>();
+ private final ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();
+ private final ArrayList<ResourcesLoader> mLoaders = new ArrayList<>();
private boolean mNoInit = false;
@@ -1625,6 +1625,23 @@ public final class AssetManager implements AutoCloseable {
}
/**
+ * Passes the display id and device id to AssetManager, to filter out overlays based on
+ * any {@link android.content.om.OverlayConstraint}.
+ *
+ * @hide
+ */
+ public void setOverlayConstraints(int displayId, int deviceId) {
+ if (!Flags.rroConstraints()) {
+ return;
+ }
+
+ synchronized (this) {
+ ensureValidLocked();
+ nativeSetOverlayConstraints(mObject, displayId, deviceId);
+ }
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
@@ -1717,6 +1734,7 @@ public final class AssetManager implements AutoCloseable {
int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
int majorVersion, boolean forceRefresh);
+ private static native void nativeSetOverlayConstraints(long ptr, int displayId, int deviceId);
private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
long ptr, boolean includeOverlays, boolean includeLoaders);
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index ef200c328d63..2e0999410483 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2358,8 +2358,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* @param locales The locale list. If null, an empty LocaleList will be assigned.
*/
public void setLocales(@Nullable LocaleList locales) {
+ LocaleList oldList = mLocaleList;
mLocaleList = locales == null ? LocaleList.getEmptyLocaleList() : locales;
locale = mLocaleList.get(0);
+ if (!mLocaleList.equals(oldList)) {
+ Slog.v(TAG, "Updating configuration, locales updated from " + oldList
+ + " to " + mLocaleList);
+ }
setLayoutDirection(locale);
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 96c71765d102..eec30f38b415 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -491,6 +491,9 @@ public class ResourcesImpl {
}
defaultLocale =
adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+ Slog.v(TAG, "Updating configuration, with default locale "
+ + defaultLocale + " and selected locales "
+ + Arrays.toString(selectedLocales));
} else {
String[] availableLocales;
// The LocaleList has changed. We must query the AssetManager's
@@ -526,6 +529,7 @@ public class ResourcesImpl {
for (int i = 0; i < locales.size(); i++) {
selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
}
+ defaultLocale = adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
} else {
selectedLocales = new String[]{
adjustLanguageTag(locales.get(0).toLanguageTag())};
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 7850e377ec4d..92a56fc575e3 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -851,6 +851,12 @@ public final class DisplayManager {
* Registers a display listener to receive notifications about when
* displays are added, removed or changed.
*
+ * Because of the high frequency at which the refresh rate can change, clients will be
+ * registered for refresh rate change callbacks only when they request for refresh rate
+ * data({@link Display#getRefreshRate()}. Or alternately, you can consider using
+ * {@link #registerDisplayListener(Executor, long, DisplayListener)} and explicitly subscribe to
+ * {@link #EVENT_TYPE_DISPLAY_REFRESH_RATE} event
+ *
* We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
* instead to subscribe for explicit events of interest
*
@@ -863,8 +869,8 @@ public final class DisplayManager {
public void registerDisplayListener(DisplayListener listener, Handler handler) {
registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
| EVENT_TYPE_DISPLAY_CHANGED
- | EVENT_TYPE_DISPLAY_REFRESH_RATE
- | EVENT_TYPE_DISPLAY_REMOVED);
+ | EVENT_TYPE_DISPLAY_REMOVED, 0,
+ ActivityThread.currentPackageName(), /* isEventFilterExplicit */ false);
}
/**
@@ -882,9 +888,8 @@ public final class DisplayManager {
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @EventType long eventFilter) {
- mGlobal.registerDisplayListener(listener, handler,
- mGlobal.mapFiltersToInternalEventFlag(eventFilter, 0),
- ActivityThread.currentPackageName());
+ registerDisplayListener(listener, handler, eventFilter, 0,
+ ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
}
/**
@@ -901,9 +906,8 @@ public final class DisplayManager {
@FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
public void registerDisplayListener(@NonNull Executor executor, @EventType long eventFilter,
@NonNull DisplayListener listener) {
- mGlobal.registerDisplayListener(listener, executor,
- mGlobal.mapFiltersToInternalEventFlag(eventFilter, 0),
- ActivityThread.currentPackageName());
+ registerDisplayListener(listener, executor, eventFilter, 0,
+ ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
}
/**
@@ -924,9 +928,39 @@ public final class DisplayManager {
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @EventType long eventFilter,
@PrivateEventType long privateEventFilter) {
+ registerDisplayListener(listener, handler, eventFilter, privateEventFilter,
+ ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
+ }
+
+ /**
+ * Registers a display listener to receive notifications about given display event types.
+ *
+ * @param listener The listener to register.
+ * @param handler The handler on which the listener should be invoked, or null
+ * if the listener should be invoked on the calling thread's looper.
+ * @param eventFilter A bitmask of the event types for which this listener is subscribed.
+ * @param privateEventFilter A bitmask of the private event types for which this listener
+ * is subscribed.
+ * @param isEventFilterExplicit Indicates if the client explicitly supplied the display events
+ * to be subscribed to.
+ *
+ */
+ private void registerDisplayListener(@NonNull DisplayListener listener,
+ @Nullable Handler handler, @EventType long eventFilter,
+ @PrivateEventType long privateEventFilter, String packageName,
+ boolean isEventFilterExplicit) {
mGlobal.registerDisplayListener(listener, handler,
mGlobal.mapFiltersToInternalEventFlag(eventFilter, privateEventFilter),
- ActivityThread.currentPackageName());
+ packageName, /* isEventFilterExplicit */ isEventFilterExplicit);
+ }
+
+ private void registerDisplayListener(@NonNull DisplayListener listener,
+ Executor executor, @EventType long eventFilter,
+ @PrivateEventType long privateEventFilter, String packageName,
+ boolean isEventFilterExplicit) {
+ mGlobal.registerDisplayListener(listener, executor,
+ mGlobal.mapFiltersToInternalEventFlag(eventFilter, privateEventFilter),
+ packageName, /* isEventFilterExplicit */ isEventFilterExplicit);
}
/**
@@ -1146,6 +1180,28 @@ public final class DisplayManager {
}
/**
+ * Resets the behavior that automatically registers clients for refresh rate change callbacks
+ * when they register via {@link #registerDisplayListener(DisplayListener, Handler)}
+ *
+ * <p>By default, clients are not registered for refresh rate change callbacks via
+ * {@link #registerDisplayListener(DisplayListener, Handler)}. However, calling
+ * {@link Display#getRefreshRate()} triggers automatic registration for existing and future
+ * {@link DisplayListener} instances. This method reverts this behavior, preventing new
+ * clients from being automatically registered for refresh rate change callbacks. Note that the
+ * existing ones will continue to stay registered
+ *
+ * <p>In essence, this method returns the system to its initial state, where explicit calls to
+ * {{@link Display#getRefreshRate()} are required to receive refresh rate change notifications.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+ @TestApi
+ public void resetImplicitRefreshRateCallbackStatus() {
+ mGlobal.resetImplicitRefreshRateCallbackStatus();
+ }
+
+ /**
* Overrides HDR modes for a display device.
*
* @hide
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a7d610e54e2c..c4af87116eed 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -187,6 +187,9 @@ public final class DisplayManagerGlobal {
private final Binder mToken = new Binder();
+ // Guarded by mLock
+ private boolean mShouldImplicitlyRegisterRrChanges = false;
+
@VisibleForTesting
public DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
@@ -390,27 +393,49 @@ public final class DisplayManagerGlobal {
* the handler for the main thread.
* If that is still null, a runtime exception will be thrown.
* @param packageName of the calling package.
+ * @param isEventFilterExplicit Indicates if the client explicitly supplied the display events
+ * to be subscribed to.
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask,
- String packageName) {
+ String packageName, boolean isEventFilterExplicit) {
Looper looper = getLooperForHandler(handler);
Handler springBoard = new Handler(looper);
registerDisplayListener(listener, new HandlerExecutor(springBoard), internalEventFlagsMask,
- packageName);
+ packageName, isEventFilterExplicit);
}
/**
* Register a listener for display-related changes.
*
* @param listener The listener that will be called when display changes occur.
+ * @param handler Handler for the thread that will be receiving the callbacks. May be null.
+ * If null, listener will use the handler for the current thread, and if still null,
+ * the handler for the main thread.
+ * If that is still null, a runtime exception will be thrown.
+ * @param internalEventFlagsMask Mask of events to be listened to.
+ * @param packageName of the calling package.
+ */
+ public void registerDisplayListener(@NonNull DisplayListener listener,
+ @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask,
+ String packageName) {
+ registerDisplayListener(listener, handler, internalEventFlagsMask, packageName, true);
+ }
+
+
+ /**
+ * Register a listener for display-related changes.
+ *
+ * @param listener The listener that will be called when display changes occur.
* @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
* @param internalEventFlagsMask Mask of events to be listened to.
* @param packageName of the calling package.
+ * @param isEventFilterExplicit Indicates if the explicit events to be subscribed to
+ * were supplied or not
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@NonNull Executor executor, @InternalEventFlag long internalEventFlagsMask,
- String packageName) {
+ String packageName, boolean isEventFilterExplicit) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
@@ -429,7 +454,7 @@ public final class DisplayManagerGlobal {
int index = findDisplayListenerLocked(listener);
if (index < 0) {
mDisplayListeners.add(new DisplayListenerDelegate(listener, executor,
- internalEventFlagsMask, packageName));
+ internalEventFlagsMask, packageName, isEventFilterExplicit));
registerCallbackIfNeededLocked();
} else {
mDisplayListeners.get(index).setEventsMask(internalEventFlagsMask);
@@ -439,6 +464,22 @@ public final class DisplayManagerGlobal {
}
}
+
+ /**
+ * Registers all the clients to INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE events if qualified
+ */
+ public void registerForRefreshRateChanges() {
+ if (!Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (!mShouldImplicitlyRegisterRrChanges) {
+ mShouldImplicitlyRegisterRrChanges = true;
+ updateCallbackIfNeededLocked();
+ }
+ }
+ }
+
public void unregisterDisplayListener(DisplayListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
@@ -521,8 +562,14 @@ public final class DisplayManagerGlobal {
long mask = 0;
final int numListeners = mDisplayListeners.size();
for (int i = 0; i < numListeners; i++) {
- mask |= mDisplayListeners.get(i).mInternalEventFlagsMask;
+ DisplayListenerDelegate displayListenerDelegate = mDisplayListeners.get(i);
+ if (!Flags.delayImplicitRrRegistrationUntilRrAccessed()
+ || mShouldImplicitlyRegisterRrChanges) {
+ displayListenerDelegate.implicitlyRegisterForRRChanges();
+ }
+ mask |= displayListenerDelegate.mInternalEventFlagsMask;
}
+
if (mDispatchNativeCallbacks) {
mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED
| INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
@@ -802,6 +849,18 @@ public final class DisplayManagerGlobal {
}
/**
+ * Resets the implicit registration of refresh rate change callbacks
+ *
+ */
+ public void resetImplicitRefreshRateCallbackStatus() {
+ if (Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+ synchronized (mLock) {
+ mShouldImplicitlyRegisterRrChanges = false;
+ }
+ }
+ }
+
+ /**
* Overrides HDR modes for a display device.
*
*/
@@ -1439,21 +1498,27 @@ public final class DisplayManagerGlobal {
}
}
- private static final class DisplayListenerDelegate {
+ @VisibleForTesting
+ static final class DisplayListenerDelegate {
public final DisplayListener mListener;
public volatile long mInternalEventFlagsMask;
+ // Indicates if the client explicitly supplied the display events to be subscribed to.
+ private final boolean mIsEventFilterExplicit;
+
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Executor mExecutor;
private AtomicLong mGenerationId = new AtomicLong(1);
private final String mPackageName;
DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
- @InternalEventFlag long internalEventFlag, String packageName) {
+ @InternalEventFlag long internalEventFlag, String packageName,
+ boolean isEventFilterExplicit) {
mExecutor = executor;
mListener = listener;
mInternalEventFlagsMask = internalEventFlag;
mPackageName = packageName;
+ mIsEventFilterExplicit = isEventFilterExplicit;
}
void sendDisplayEvent(int displayId, @DisplayEvent int event, @Nullable DisplayInfo info,
@@ -1470,6 +1535,11 @@ public final class DisplayManagerGlobal {
});
}
+ @VisibleForTesting
+ boolean isEventFilterExplicit() {
+ return mIsEventFilterExplicit;
+ }
+
void clearEvents() {
mGenerationId.incrementAndGet();
}
@@ -1478,6 +1548,17 @@ public final class DisplayManagerGlobal {
mInternalEventFlagsMask = newInternalEventFlagsMask;
}
+ private void implicitlyRegisterForRRChanges() {
+ // For backward compatibility, if the user didn't supply the explicit events while
+ // subscribing, register them to refresh rate change events if they subscribed to
+ // display changed events
+ if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) != 0
+ && !mIsEventFilterExplicit) {
+ setEventsMask(mInternalEventFlagsMask
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE);
+ }
+ }
+
private void handleDisplayEventInner(int displayId, @DisplayEvent int event,
@Nullable DisplayInfo info, boolean forceUpdate) {
if (extraLogging()) {
@@ -1677,6 +1758,9 @@ public final class DisplayManagerGlobal {
public void registerNativeChoreographerForRefreshRateCallbacks() {
synchronized (mLock) {
mDispatchNativeCallbacks = true;
+ if (Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+ mShouldImplicitlyRegisterRrChanges = true;
+ }
registerCallbackIfNeededLocked();
updateCallbackIfNeededLocked();
DisplayInfo display = getDisplayInfoLocked(Display.DEFAULT_DISPLAY);
@@ -1806,4 +1890,9 @@ public final class DisplayManagerGlobal {
return baseEventMask;
}
+
+ @VisibleForTesting
+ CopyOnWriteArrayList<DisplayListenerDelegate> getDisplayListeners() {
+ return mDisplayListeners;
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index a528ba4b16bf..7b47efd47008 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -990,6 +990,8 @@ public class InputMethodService extends AbstractInputMethodService {
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // After the IME window was hidden, we can remove its surface
+ scheduleImeSurfaceRemoval();
// The hide request first finishes the animation and then proceeds to the server
// side, finally reaching here, marking this the end state.
ImeTracker.forLogging().onHidden(statsToken);
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index adb98aa25f8f..68442293c3a3 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -23,6 +23,8 @@ import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
@@ -50,6 +52,7 @@ public final class PerfettoTrackEventExtra {
private static final Supplier<FieldString> sFieldStringSupplier = FieldString::new;
private static final Supplier<FieldNested> sFieldNestedSupplier = FieldNested::new;
+ private final List<PerfettoPointer> mPendingPointers = new ArrayList<>();
private CounterInt64 mCounterInt64;
private CounterDouble mCounterDouble;
private Proto mProto;
@@ -592,7 +595,7 @@ public final class PerfettoTrackEventExtra {
checkContainer();
FieldInt64 field = mFieldInt64Cache.get(sFieldInt64Supplier);
field.setValue(id, val);
- mCurrentContainer.addField(field);
+ mExtra.addPerfettoPointer(mCurrentContainer, field);
return this;
}
@@ -601,7 +604,7 @@ public final class PerfettoTrackEventExtra {
checkContainer();
FieldDouble field = mFieldDoubleCache.get(sFieldDoubleSupplier);
field.setValue(id, val);
- mCurrentContainer.addField(field);
+ mExtra.addPerfettoPointer(mCurrentContainer, field);
return this;
}
@@ -610,7 +613,7 @@ public final class PerfettoTrackEventExtra {
checkContainer();
FieldString field = mFieldStringCache.get(sFieldStringSupplier);
field.setValue(id, val);
- mCurrentContainer.addField(field);
+ mExtra.addPerfettoPointer(mCurrentContainer, field);
return this;
}
@@ -635,7 +638,7 @@ public final class PerfettoTrackEventExtra {
checkContainer();
FieldNested field = mFieldNestedCache.get(sFieldNestedSupplier);
field.setId(id);
- mCurrentContainer.addField(field);
+ mExtra.addPerfettoPointer(mCurrentContainer, field);
return mBuilderCache.get(sBuilderSupplier).initInternal(this, field);
}
@@ -735,6 +738,15 @@ public final class PerfettoTrackEventExtra {
*/
public void addPerfettoPointer(PerfettoPointer extra) {
native_add_arg(mPtr, extra.getPtr());
+ mPendingPointers.add(extra);
+ }
+
+ /**
+ * Adds a pointer representing a track event parameter to the {@code container}.
+ */
+ public void addPerfettoPointer(FieldContainer container, PerfettoPointer extra) {
+ container.addField(extra);
+ mPendingPointers.add(extra);
}
/**
@@ -742,6 +754,7 @@ public final class PerfettoTrackEventExtra {
*/
public void reset() {
native_clear_args(mPtr);
+ mPendingPointers.clear();
}
private CounterInt64 getCounterInt64() {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 7ceb948945fd..0615578935a1 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -145,3 +145,13 @@ flag {
purpose: PURPOSE_FEATURE
}
}
+
+flag {
+ namespace: "haptics"
+ name: "fix_vibration_thread_callback_handling"
+ description: "Fix how the VibrationThread handles late callbacks from the vibrator HAL"
+ bug: "395005081"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4ebfe53cab58..538283e10738 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13017,18 +13017,16 @@ public final class Settings {
* false/0.
* @hide
*/
- @Readable
public static final String REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI =
"redact_otp_on_wifi";
/**
- * Toggle for whether to immediately redact OTP notifications, or require the device to be
- * locked for 10 minutes. Defaults to false/0
+ * Time (in milliseconds) that the device should need to be locked, in order for an OTP
+ * notification to be redacted. Default is 10 minutes (600,000 ms)
* @hide
*/
- @Readable
- public static final String REDACT_OTP_NOTIFICATION_IMMEDIATELY =
- "remove_otp_redaction_delay";
+ public static final String OTP_NOTIFICATION_REDACTION_LOCK_TIME =
+ "otp_redaction_lock_time";
/**
* These entries are considered common between the personal and the managed profile,
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 6f94c1b2d274..4cbd5beb3a8c 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -3131,6 +3131,7 @@ public class ZenModeConfig implements Parcelable {
* @return null if DND is off or describeForeverCondition is false and
* DND is on forever (until turned off)
*/
+ // TODO: b/368247671 - Delete when inlining MODES_UI
public static String getDescription(Context context, boolean zenOn, ZenModeConfig config,
boolean describeForeverCondition) {
if (!zenOn || config == null) {
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 2fa56137a8a0..b273a7f7c271 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1071,13 +1071,27 @@ public abstract class Layout {
var newBackground = determineContrastingBackgroundColor(index);
var hasBgColorChanged = newBackground != bgPaint.getColor();
- if (lineNum != mLastLineNum || hasBgColorChanged) {
- // Skip processing if the character is a space or a tap to avoid
- // rendering an abrupt, empty rectangle.
- if (Character.isWhitespace(mText.charAt(index))) {
- return;
- }
+ // Skip processing if the character is a space or a tap to avoid
+ // rendering an abrupt, empty rectangle.
+ if (TextLine.isLineEndSpace(mText.charAt(index))) {
+ return;
+ }
+ // To avoid highlighting emoji sequences, we use Extended_Pictgraphs as a
+ // heuristic. Highlighting is skipped based on code points, not glyph type
+ // (text vs. color), so emojis with default text presentation are
+ // intentionally not highlighted (numeric representation with emoji
+ // presentation are manually excluded). Although we process ZWJ and
+ // variation selectors within emoji sequences, they should not affect
+ // highlighting due to their zero-width nature.
+ var codePoint = Character.codePointAt(mText, index);
+ var isEmoji = Character.isEmojiComponent(codePoint)
+ || Character.isExtendedPictographic(codePoint);
+ if (isEmoji && !isStandardNumber(index)) {
+ return;
+ }
+
+ if (lineNum != mLastLineNum || hasBgColorChanged) {
// Draw what we have so far, then reset the rect and update its color
drawRect();
mLineBackground.set(left, top, right, bottom);
@@ -1096,6 +1110,16 @@ public abstract class Layout {
drawRect();
}
+ private boolean isStandardNumber(int index) {
+ var codePoint = Character.codePointAt(mText, index);
+ var isNumberSignOrAsterisk = (codePoint >= '0' && codePoint <= '9')
+ || codePoint == '#' || codePoint == '*';
+ var isColoredGlyph = index + 1 < mText.length()
+ && Character.codePointAt(mText, index + 1) == 0xFE0F;
+
+ return isNumberSignOrAsterisk && !isColoredGlyph;
+ }
+
private void drawRect() {
if (!mLineBackground.isEmpty()) {
mLineBackground.inset(-padding, -padding);
@@ -4626,6 +4650,16 @@ public abstract class Layout {
* Callback for {@link #forEachCharacterBounds(int, int, int, int, CharacterBoundsListener)}
*/
private interface CharacterBoundsListener {
+ /**
+ * Called for each character with its bounds.
+ *
+ * @param index the index of the character
+ * @param lineNum the line number of the character
+ * @param left the left edge of the character
+ * @param top the top edge of the character
+ * @param right the right edge of the character
+ * @param bottom the bottom edge of the character
+ */
void onCharacterBounds(int index, int lineNum, float left, float top, float right,
float bottom);
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 231aa6816908..3f45e29f2d43 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -108,6 +108,7 @@ public final class Display {
private final String mOwnerPackageName;
private final Resources mResources;
private DisplayAdjustments mDisplayAdjustments;
+ private boolean mRefreshRateChangesRegistered;
@UnsupportedAppUsage
private DisplayInfo mDisplayInfo; // never null
@@ -1217,6 +1218,10 @@ public final class Display {
*/
public float getRefreshRate() {
synchronized (mLock) {
+ if (!mRefreshRateChangesRegistered) {
+ DisplayManagerGlobal.getInstance().registerForRefreshRateChanges();
+ mRefreshRateChangesRegistered = true;
+ }
updateDisplayInfoLocked();
return mDisplayInfo.getRefreshRate();
}
@@ -1601,7 +1606,7 @@ public final class Display {
.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
| DisplayManagerGlobal
.INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
- ActivityThread.currentPackageName());
+ ActivityThread.currentPackageName(), /* isEventFilterImplicit */ true);
}
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c174fbe0bbcd..3659e785f590 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1299,7 +1299,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// Handle the pending show request for other insets types since the IME insets
// has being requested hidden.
handlePendingControlRequest(statsToken);
- getImeSourceConsumer().removeSurface();
+ if (!Flags.refactorInsetsController()) {
+ // the surface can't be removed until the end of the animation. This is handled by
+ // IMMS after the window was requested to be hidden.
+ getImeSourceConsumer().removeSurface();
+ }
}
applyAnimation(typesReady, false /* show */, fromIme, false /* skipsCallbacks */,
statsToken);
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index df680c054f56..fe868e1c43be 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -223,8 +223,14 @@ public class NotificationHeaderView extends RelativeLayout {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (notificationsRedesignTemplates()) {
- mTopLineTranslation = measureCenterTranslation(mTopLineView);
- mExpandButtonTranslation = measureCenterTranslation(mExpandButton);
+ // TODO: b/378660052 - These should never be null in practice, consider using
+ // requireViewById() in the onFinishInflate.
+ if (mTopLineView != null) {
+ mTopLineTranslation = measureCenterTranslation(mTopLineView);
+ }
+ if (mExpandButton != null) {
+ mExpandButtonTranslation = measureCenterTranslation(mExpandButton);
+ }
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0866e0d832b1..c048d7987515 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -66,6 +66,7 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
import static com.android.window.flags.Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW;
+import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs;
import static java.lang.Math.max;
@@ -247,6 +248,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@@ -12888,15 +12890,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (rects.isEmpty() && mListenerInfo == null) return;
final ListenerInfo info = getListenerInfo();
+ final boolean rectsChanged = !reduceChangedExclusionRectsMsgs()
+ || !Objects.equals(info.mSystemGestureExclusionRects, rects);
if (info.mSystemGestureExclusionRects != null) {
- info.mSystemGestureExclusionRects.clear();
- info.mSystemGestureExclusionRects.addAll(rects);
+ if (rectsChanged) {
+ info.mSystemGestureExclusionRects.clear();
+ info.mSystemGestureExclusionRects.addAll(rects);
+ }
} else {
info.mSystemGestureExclusionRects = new ArrayList<>(rects);
}
-
- updatePositionUpdateListener();
- postUpdate(this::updateSystemGestureExclusionRects);
+ if (rectsChanged) {
+ updatePositionUpdateListener();
+ postUpdate(this::updateSystemGestureExclusionRects);
+ }
}
private void updatePositionUpdateListener() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cfdcc10a20f4..9498407fb33b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -136,6 +136,7 @@ import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
import static com.android.window.flags.Flags.enableWindowContextResourcesUpdateOnConfigChange;
import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
+import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs;
import static com.android.window.flags.Flags.setScPropertiesInClient;
import android.Manifest;
@@ -152,8 +153,6 @@ import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ResourcesManager;
-import android.app.UiModeManager;
-import android.app.UiModeManager.ForceInvertStateChangeListener;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
import android.app.servertransaction.WindowStateTransactionItem;
@@ -169,6 +168,7 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.database.ContentObserver;
import android.graphics.BLASTBufferQueue;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -214,6 +214,7 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.provider.Settings;
import android.sysprop.DisplayProperties;
import android.sysprop.ViewProperties;
import android.text.TextUtils;
@@ -469,8 +470,10 @@ public final class ViewRootImpl implements ViewParent,
private CompatOnBackInvokedCallback mCompatOnBackInvokedCallback;
@Nullable
- private ForceInvertStateChangeListener mForceInvertStateChangeListener;
+ private ContentObserver mForceInvertObserver;
+ private static final int INVALID_VALUE = Integer.MIN_VALUE;
+ private int mForceInvertEnabled = INVALID_VALUE;
/**
* Callback for notifying about global configuration changes.
*/
@@ -552,8 +555,6 @@ public final class ViewRootImpl implements ViewParent,
@UiContext
public final Context mContext;
- private UiModeManager mUiModeManager;
-
@UnsupportedAppUsage
final IWindowSession mWindowSession;
@NonNull Display mDisplay;
@@ -1255,7 +1256,6 @@ public final class ViewRootImpl implements ViewParent,
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
WindowLayout windowLayout) {
mContext = context;
- mUiModeManager = context.getSystemService(UiModeManager.class);
mWindowSession = session;
mWindowLayout = windowLayout;
mDisplay = display;
@@ -1804,6 +1804,23 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ private boolean isForceInvertEnabled() {
+ if (mForceInvertEnabled == INVALID_VALUE) {
+ reloadForceInvertEnabled();
+ }
+ return mForceInvertEnabled == 1;
+ }
+
+ private void reloadForceInvertEnabled() {
+ if (forceInvertColor()) {
+ mForceInvertEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* def= */ 0,
+ UserHandle.myUserId());
+ }
+ }
+
/**
* Register any kind of listeners if setView was success.
*/
@@ -1835,11 +1852,21 @@ public final class ViewRootImpl implements ViewParent,
mBasePackageName);
if (forceInvertColor()) {
- if (mForceInvertStateChangeListener == null) {
- mForceInvertStateChangeListener =
- forceInvertState -> updateForceDarkMode();
- mUiModeManager.addForceInvertStateChangeListener(mExecutor,
- mForceInvertStateChangeListener);
+ if (mForceInvertObserver == null) {
+ mForceInvertObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ reloadForceInvertEnabled();
+ updateForceDarkMode();
+ }
+ };
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED
+ ),
+ false,
+ mForceInvertObserver,
+ UserHandle.myUserId());
}
}
}
@@ -1857,10 +1884,9 @@ public final class ViewRootImpl implements ViewParent,
.unregisterDisplayListener(mDisplayListener);
if (forceInvertColor()) {
- if (mForceInvertStateChangeListener != null) {
- mUiModeManager.removeForceInvertStateChangeListener(
- mForceInvertStateChangeListener);
- mForceInvertStateChangeListener = null;
+ if (mForceInvertObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(mForceInvertObserver);
+ mForceInvertObserver = null;
}
}
@@ -2047,25 +2073,21 @@ public final class ViewRootImpl implements ViewParent,
return getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
}
- /**
- * Determines the type of force dark to apply, considering force inversion, system night mode,
- * and app-specific settings (including developer opt-outs).
- *
- * @return A {@link ForceDarkType.ForceDarkTypeDef} constant indicating the force dark type.
- */
+ /** Returns true if force dark should be enabled according to various settings */
@VisibleForTesting
public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
if (forceInvertColor()) {
// Force invert ignores all developer opt-outs.
// We also ignore dark theme, since the app developer can override the user's preference
- // for dark mode in configuration.uiMode. Instead, we assume that both force invert and
- // the system's dark theme are enabled.
- if (mUiModeManager.getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK) {
+ // for dark mode in configuration.uiMode. Instead, we assume that the force invert
+ // setting will be enabled at the same time dark theme is in the Settings app.
+ if (isForceInvertEnabled()) {
return ForceDarkType.FORCE_INVERT_COLOR_DARK;
}
}
boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
+
if (useAutoDark) {
boolean forceDarkAllowedDefault =
SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
@@ -6053,8 +6075,12 @@ public final class ViewRootImpl implements ViewParent,
}
void updateSystemGestureExclusionRectsForView(View view) {
+ boolean msgInQueue = reduceChangedExclusionRectsMsgs()
+ && mGestureExclusionTracker.isWaitingForComputeChanges();
mGestureExclusionTracker.updateRectsForView(view);
- mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
+ if (!msgInQueue) {
+ mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
+ }
}
void systemGestureExclusionChanged() {
@@ -6098,8 +6124,12 @@ public final class ViewRootImpl implements ViewParent,
* the root's view hierarchy.
*/
public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) {
+ boolean msgInQueue = reduceChangedExclusionRectsMsgs()
+ && mGestureExclusionTracker.isWaitingForComputeChanges();
mGestureExclusionTracker.setRootRects(rects);
- mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
+ if (!msgInQueue) {
+ mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
+ }
}
/**
diff --git a/core/java/android/view/ViewRootRectTracker.java b/core/java/android/view/ViewRootRectTracker.java
index 152729b8d1d8..0bef3255f1dc 100644
--- a/core/java/android/view/ViewRootRectTracker.java
+++ b/core/java/android/view/ViewRootRectTracker.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.lang.ref.WeakReference;
@@ -32,8 +33,10 @@ import java.util.function.Function;
/**
* Abstract class to track a collection of rects reported by the views under the same
* {@link ViewRootImpl}.
+ * @hide
*/
-class ViewRootRectTracker {
+@VisibleForTesting
+public class ViewRootRectTracker {
private final Function<View, List<Rect>> mRectCollector;
private boolean mViewsChanged = false;
private boolean mRootRectsChanged = false;
@@ -41,11 +44,18 @@ class ViewRootRectTracker {
private List<ViewInfo> mViewInfos = new ArrayList<>();
private List<Rect> mRects = Collections.emptyList();
+ // Keeps track of whether updateRectsForView has been called but there was no subsequent call
+ // on computeChanges yet. Since updateRectsForView is called when sending a message and
+ // computeChanges when it is received, this tracks whether such message is in the queue already
+ private boolean mWaitingForComputeChanges = false;
+
/**
* @param rectCollector given a view returns a list of the rects of interest for this
* ViewRootRectTracker
+ * @hide
*/
- ViewRootRectTracker(Function<View, List<Rect>> rectCollector) {
+ @VisibleForTesting
+ public ViewRootRectTracker(Function<View, List<Rect>> rectCollector) {
mRectCollector = rectCollector;
}
@@ -70,6 +80,7 @@ class ViewRootRectTracker {
mViewInfos.add(new ViewInfo(view));
mViewsChanged = true;
}
+ mWaitingForComputeChanges = true;
}
/**
@@ -92,6 +103,7 @@ class ViewRootRectTracker {
* @return {@code true} if there were changes, {@code false} otherwise.
*/
public boolean computeChanges() {
+ mWaitingForComputeChanges = false;
boolean changed = mRootRectsChanged;
final Iterator<ViewInfo> i = mViewInfos.iterator();
final List<Rect> rects = new ArrayList<>(mRootRects);
@@ -121,6 +133,10 @@ class ViewRootRectTracker {
return false;
}
+ public boolean isWaitingForComputeChanges() {
+ return mWaitingForComputeChanges;
+ }
+
/**
* Returns a List of all Rects from all visible Views in the global (root) coordinate system.
* This list is only updated when calling {@link #computeChanges()} or
@@ -140,6 +156,7 @@ class ViewRootRectTracker {
Preconditions.checkNotNull(rects, "rects must not be null");
mRootRects = rects;
mRootRectsChanged = true;
+ mWaitingForComputeChanges = true;
}
@NonNull
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index e3ea6b229d64..a952967f4daf 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1290,7 +1290,8 @@ public final class WindowInsets {
int newTop = Math.max(0, insets.top - top);
int newRight = Math.max(0, insets.right - right);
int newBottom = Math.max(0, insets.bottom - bottom);
- if (newLeft == left && newTop == top && newRight == right && newBottom == bottom) {
+ if (newLeft == insets.left && newTop == insets.top
+ && newRight == insets.right && newBottom == insets.bottom) {
return insets;
}
return Insets.of(newLeft, newTop, newRight, newBottom);
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index b44620f12bbb..4ba97384192f 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -105,6 +105,7 @@ public enum DesktopModeFlags {
ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true),
+ ENABLE_TASKBAR_OVERFLOW(Flags::enableTaskbarOverflow, false),
ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true),
ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 891c5e382a58..8265d2523d6f 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -196,6 +196,16 @@ flag {
}
flag {
+ name: "enable_camera_compat_for_desktop_windowing_opt_out"
+ namespace: "lse_desktop_experience"
+ description: "Whether to allow developers to opt-out of Camera Compat treatment to fixed-orientation apps in desktop windowing mode"
+ bug: "328616176"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_task_stack_observer_in_shell"
namespace: "lse_desktop_experience"
description: "Introduces a new observer in shell to track the task stack."
@@ -695,6 +705,13 @@ flag {
}
flag {
+ name: "enable_activity_embedding_support_for_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enables activity embedding support for connected displays, including enabling AE optimization for Settings."
+ bug: "369438353"
+}
+
+flag {
name: "enable_full_screen_window_on_removing_split_screen_stage_bugfix"
namespace: "lse_desktop_experience"
description: "Enables clearing the windowing mode of a freeform window when removing the task from the split screen stage."
@@ -754,3 +771,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_taskbar_overflow"
+ namespace: "lse_desktop_experience"
+ description: "Show recent apps in the taskbar overflow."
+ bug: "368119679"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 60f2c811dd1f..a4d128fa3caf 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -428,6 +428,17 @@ flag {
}
flag {
+ name: "reduce_changed_exclusion_rects_msgs"
+ namespace: "windowing_frontend"
+ description: "Don't send MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED when there is no change"
+ bug: "388231176"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "keep_app_window_hide_while_locked"
namespace: "windowing_frontend"
description: "Do not let app window visible while device is locked"
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 2931bd2c83dd..fe616e085488 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
@@ -155,8 +156,9 @@ public final class LongArrayMultiStateCounter implements Parcelable {
/**
* Adds the supplied values to the current accumulated values in the counter.
+ * Null `values` parameter is interpreted as an array of zeros.
*/
- public void incrementValues(long[] values, long timestampMs) {
+ public void incrementValues(@Nullable long[] values, long timestampMs) {
native_incrementValues(mNativeObject, values, timestampMs);
}
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
index 7030d8e84b70..4f5f37d0640e 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.annotation.Nullable;
import android.os.BadParcelableException;
import android.os.Parcel;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
@@ -147,10 +148,12 @@ class LongArrayMultiStateCounter_ravenwood {
mLastUpdateTimestampMs = timestampMs;
}
- public void incrementValues(long[] delta, long timestampMs) {
+ public void incrementValues(@Nullable long[] delta, long timestampMs) {
long[] values = Arrays.copyOf(mValues, mValues.length);
- for (int i = 0; i < mArrayLength; i++) {
- values[i] += delta[i];
+ if (delta != null) {
+ for (int i = 0; i < mArrayLength; i++) {
+ values[i] += delta[i];
+ }
}
updateValue(values, timestampMs);
}
@@ -304,7 +307,8 @@ class LongArrayMultiStateCounter_ravenwood {
getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId));
}
- public static void native_incrementValues(long instanceId, long[] delta, long timestampMs) {
+ public static void native_incrementValues(long instanceId, @Nullable long[] delta,
+ long timestampMs) {
getInstance(instanceId).incrementValues(delta, timestampMs);
}
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index aafef6c8b5fd..6c69e2c623ad 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -128,6 +128,7 @@ public final class PowerStats {
* Extra parameters specific to the power component, e.g. the availability of power
* monitors.
*/
+ @NonNull
public final PersistableBundle extras;
private PowerStatsFormatter mDeviceStatsFormatter;
@@ -269,20 +270,41 @@ public final class PowerStats {
stateStatsArrayLength, uidStatsArrayLength, extras);
}
+ @SuppressWarnings("deprecation")
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Descriptor)) return false;
Descriptor that = (Descriptor) o;
- return powerComponentId == that.powerComponentId
- && statsArrayLength == that.statsArrayLength
- && stateLabels.contentEquals(that.stateLabels)
- && stateStatsArrayLength == that.stateStatsArrayLength
- && uidStatsArrayLength == that.uidStatsArrayLength
- && Objects.equals(name, that.name)
- && extras.size() == that.extras.size() // Unparcel the Parcel if not yet
- && Bundle.kindofEquals(extras,
- that.extras); // Since the Parcel is now unparceled, do a deep comparison
+ if (powerComponentId != that.powerComponentId
+ || statsArrayLength != that.statsArrayLength
+ || !stateLabels.contentEquals(that.stateLabels)
+ || stateStatsArrayLength != that.stateStatsArrayLength
+ || uidStatsArrayLength != that.uidStatsArrayLength
+ || !Objects.equals(name, that.name)) {
+ return false;
+ }
+
+ // Getting the size has the side-effect of unparceling the Bundle if not yet
+ if (extras.size() != that.extras.size()) {
+ return false;
+ }
+
+ if (Bundle.kindofEquals(extras, that.extras)) {
+ return true;
+ }
+
+ // Since `kindofEquals` does not deep-compare arrays, we do that separately, albeit at
+ // the expense of creating an iterator and using a deprecated API, `bundle.get`.
+ // There is no performance concern, because the situation where PowerStatsDescriptors
+ // are changed in an incompatible way are exceedingly rare, occurring at most
+ // once per power component after a system upgrade.
+ for (String key : extras.keySet()) {
+ if (!Objects.deepEquals(extras.get(key), that.extras.get(key))) {
+ return false;
+ }
+ }
+ return true;
}
/**
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index b57acf3d97fd..b19967a47001 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -261,7 +261,12 @@ public class AconfigFlags {
// Default value is false for when the flag is not found.
// Note: Unlike with the old storage, with AconfigPackage, we don't have a way to
// know if the flag is not found or if it's found but the value is false.
- value = aconfigPackage.getBooleanFlagValue(flagName, false);
+ try {
+ value = aconfigPackage.getBooleanFlagValue(flagName, false);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Failed to read Aconfig flag value for " + flagPackageAndName, e);
+ return null;
+ }
}
if (DEBUG) {
Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
index 7ee22f30ace0..69c04807c604 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
@@ -157,7 +157,7 @@ public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(this.name);
+ sForInternedString.parcel(this.name, dest, flags);
dest.writeInt(this.getIcon());
dest.writeInt(this.getLabelRes());
dest.writeCharSequence(this.getNonLocalizedLabel());
@@ -175,7 +175,7 @@ public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable
// We use the boot classloader for all classes that we load.
final ClassLoader boot = Object.class.getClassLoader();
//noinspection ConstantConditions
- this.name = in.readString();
+ this.name = sForInternedString.unparcel(in);
this.icon = in.readInt();
this.labelRes = in.readInt();
this.nonLocalizedLabel = in.readCharSequence();
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 93be3b02a12a..2d989943800e 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -322,7 +322,7 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
PrintWriter pw = shell.getOutPrintWriter();
if (android.tracing.Flags.clientSideProtoLogging()) {
- pw.println("Command deprecated. Please use 'cmd protolog' instead.");
+ pw.println("Command deprecated. Please use 'cmd protolog_configuration' instead.");
return -1;
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
index 82d8d3431a9d..6d4a40899a65 100644
--- a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
+++ b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
@@ -145,11 +145,11 @@ public class ProtoLogCommandHandler extends ShellCommand {
switch (cmd) {
case "enable" -> {
- mProtoLogConfigurationService.enableProtoLogToLogcat(processGroups());
+ mProtoLogConfigurationService.enableProtoLogToLogcat(pw, processGroups());
return 0;
}
case "disable" -> {
- mProtoLogConfigurationService.disableProtoLogToLogcat(processGroups());
+ mProtoLogConfigurationService.disableProtoLogToLogcat(pw, processGroups());
return 0;
}
default -> {
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
index d65aaae7deaa..a19690bbd0e4 100644
--- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
@@ -18,6 +18,8 @@ package com.android.internal.protolog;
import android.annotation.NonNull;
+import java.io.PrintWriter;
+
public interface ProtoLogConfigurationService extends IProtoLogConfigurationService {
/**
* Get the list of groups clients have registered to the protolog service.
@@ -37,11 +39,11 @@ public interface ProtoLogConfigurationService extends IProtoLogConfigurationServ
* Enable logging target groups to logcat.
* @param groups we want to enable logging them to logcat for.
*/
- void enableProtoLogToLogcat(@NonNull String... groups);
+ void enableProtoLogToLogcat(@NonNull PrintWriter pw, @NonNull String... groups);
/**
* Disable logging target groups to logcat.
* @param groups we want to disable from being logged to logcat.
*/
- void disableProtoLogToLogcat(@NonNull String... groups);
+ void disableProtoLogToLogcat(@NonNull PrintWriter pw, @NonNull String... groups);
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
index f83359dddfcc..ac1022ff1e0f 100644
--- a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java
@@ -44,6 +44,7 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -183,8 +184,8 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
* @param groups we want to enable logging them to logcat for.
*/
@Override
- public void enableProtoLogToLogcat(@NonNull String... groups) {
- toggleProtoLogToLogcat(true, groups);
+ public void enableProtoLogToLogcat(@NonNull PrintWriter pw, @NonNull String... groups) {
+ toggleProtoLogToLogcat(pw, true, groups);
}
/**
@@ -192,8 +193,8 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
* @param groups we want to disable from being logged to logcat.
*/
@Override
- public void disableProtoLogToLogcat(@NonNull String... groups) {
- toggleProtoLogToLogcat(false, groups);
+ public void disableProtoLogToLogcat(@NonNull PrintWriter pw, @NonNull String... groups) {
+ toggleProtoLogToLogcat(pw, false, groups);
}
/**
@@ -249,7 +250,9 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
}
}
- private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) {
+ private void toggleProtoLogToLogcat(
+ @NonNull PrintWriter pw, boolean enabled, @NonNull String[] groups
+ ) {
final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>();
for (String group : groups) {
@@ -257,8 +260,10 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
if (clients == null) {
// No clients associated to this group
- Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group
- + " with no registered clients.");
+ var warning = "Attempting to toggle log to logcat for group " + group
+ + " with no registered clients. This is a no-op.";
+ Log.w(LOG_TAG, warning);
+ pw.println("WARNING: " + warning);
continue;
}
@@ -270,8 +275,14 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
for (IProtoLogClient client : clientToGroups.keySet()) {
try {
- client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0]));
+ final var clientGroups = clientToGroups.get(client).toArray(new String[0]);
+ pw.println("Toggling logcat logging for client " + client.toString()
+ + " to " + enabled + " for groups: ["
+ + String.join(", ", clientGroups) + "]");
+ client.toggleLogcat(enabled, clientGroups);
+ pw.println("- Done");
} catch (RemoteException e) {
+ pw.println("- Failed");
throw new RuntimeException(
"Failed to toggle logcat status for groups on client", e);
}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 641ecc9b675a..ce46da12aa76 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -250,7 +250,8 @@ public class ConversationLayout extends FrameLayout
mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
});
- mConversationText = findViewById(R.id.conversation_text);
+ mConversationText = findViewById(notificationsRedesignTemplates()
+ ? R.id.title : R.id.conversation_text);
mExpandButtonContainer = findViewById(R.id.expand_button_container);
mExpandButtonContainerA11yContainer =
findViewById(R.id.expand_button_a11y_container);
@@ -716,17 +717,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateImageMessages() {
- View newMessage = null;
- if (mIsCollapsed && !mGroups.isEmpty()) {
-
- // When collapsed, we're displaying the image message in a dedicated container
- // on the right of the layout instead of inline. Let's add the isolated image there
- MessagingGroup messagingGroup = mGroups.getLast();
- MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
- if (isolatedMessage != null) {
- newMessage = isolatedMessage.getView();
- }
+ if (mImageMessageContainer == null) {
+ return;
}
+ View newMessage = getNewImageMessage();
// Remove all messages that don't belong into the image layout
View previousMessage = mImageMessageContainer.getChildAt(0);
if (previousMessage != newMessage) {
@@ -738,6 +732,20 @@ public class ConversationLayout extends FrameLayout
mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE);
}
+ @Nullable
+ private View getNewImageMessage() {
+ if (mIsCollapsed && !mGroups.isEmpty()) {
+ // When collapsed, we're displaying the image message in a dedicated container
+ // on the right of the layout instead of inline. Let's add the isolated image there
+ MessagingGroup messagingGroup = mGroups.getLast();
+ MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ return isolatedMessage.getView();
+ }
+ }
+ return null;
+ }
+
public void bindFacePile(ImageView bottomBackground, ImageView bottomView, ImageView topView) {
applyNotificationBackgroundColor(bottomBackground);
// Let's find the two last conversations:
@@ -841,6 +849,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateAppName() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
mAppName.setVisibility(mIsCollapsed ? GONE : VISIBLE);
}
@@ -1533,6 +1545,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateExpandButton() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
int buttonGravity;
ViewGroup newContainer;
if (mIsCollapsed) {
@@ -1565,6 +1581,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateContentEndPaddings() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
// Let's make sure the conversation header can't run into the expand button when we're
// collapsed and update the paddings of the content
int headerPaddingEnd;
@@ -1593,6 +1613,10 @@ public class ConversationLayout extends FrameLayout
}
private void onAppNameVisibilityChanged() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
boolean appNameGone = mAppName.getVisibility() == GONE;
if (appNameGone != mAppNameGone) {
mAppNameGone = appNameGone;
@@ -1601,10 +1625,18 @@ public class ConversationLayout extends FrameLayout
}
private void updateAppNameDividerVisibility() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
mAppNameDivider.setVisibility(mAppNameGone ? GONE : VISIBLE);
}
public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
mExpandable = expandable;
if (expandable) {
mExpandButtonContainer.setVisibility(VISIBLE);
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 31d9770f6ac4..b9a603cc5696 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -449,12 +449,8 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
}
private void updateIconVisibility() {
- if (Flags.notificationsRedesignTemplates() && !mIsInConversation) {
- // We don't show any icon (other than the app icon) in the collapsed form. For
- // conversations, keeping this container helps with aligning the message to the icon
- // when collapsed, but the old messaging style already has this alignment built into
- // the template like all other layouts. Conversations are special because we use the
- // same base layout for both the collapsed and expanded views.
+ if (Flags.notificationsRedesignTemplates()) {
+ // We don't show any icon (other than the app or person icon) in the collapsed form.
mMessagingIconContainer.setVisibility(mSingleLine ? GONE : VISIBLE);
}
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index e9d920ca3fcd..eb22e7c8cdc0 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -121,22 +121,38 @@ public class MessagingLayout extends FrameLayout
setMessagingClippingDisabled(false);
}
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setAvatarReplacementAsync")
public void setAvatarReplacement(Icon icon) {
mAvatarReplacement = icon;
}
- @RemotableViewMethod
+ /**
+ * @hide
+ */
+ public Runnable setAvatarReplacementAsync(Icon icon) {
+ mAvatarReplacement = icon;
+ return () -> {};
+ }
+
+ @RemotableViewMethod(asyncImpl = "setNameReplacementAsync")
public void setNameReplacement(CharSequence nameReplacement) {
mNameReplacement = nameReplacement;
}
/**
+ * @hide
+ */
+ public Runnable setNameReplacementAsync(CharSequence nameReplacement) {
+ mNameReplacement = nameReplacement;
+ return () -> {};
+ }
+
+ /**
* Set this layout to show the collapsed representation.
*
* @param isCollapsed is it collapsed
*/
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
}
@@ -145,7 +161,6 @@ public class MessagingLayout extends FrameLayout
* setDataAsync needs to do different stuff for the collapsed vs expanded view, so store the
* collapsed state early.
*/
- @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
public Runnable setIsCollapsedAsync(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
return () -> {};
@@ -161,12 +176,20 @@ public class MessagingLayout extends FrameLayout
*
* @param conversationTitle the conversation title
*/
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setConversationTitleAsync")
public void setConversationTitle(CharSequence conversationTitle) {
mConversationTitle = conversationTitle;
}
/**
+ * @hide
+ */
+ public Runnable setConversationTitleAsync(CharSequence conversationTitle) {
+ mConversationTitle = conversationTitle;
+ return ()->{};
+ }
+
+ /**
* Set Messaging data
* @param extras Bundle contains messaging data
*/
@@ -314,19 +337,10 @@ public class MessagingLayout extends FrameLayout
}
private void updateImageMessages() {
- View newMessage = null;
if (mImageMessageContainer == null) {
return;
}
- if (mIsCollapsed && !mGroups.isEmpty()) {
- // When collapsed, we're displaying the image message in a dedicated container
- // on the right of the layout instead of inline. Let's add the isolated image there
- MessagingGroup messagingGroup = mGroups.getLast();
- MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
- if (isolatedMessage != null) {
- newMessage = isolatedMessage.getView();
- }
- }
+ View newMessage = getNewImageMessage();
// Remove all messages that don't belong into the image layout
View previousMessage = mImageMessageContainer.getChildAt(0);
if (previousMessage != newMessage) {
@@ -345,6 +359,20 @@ public class MessagingLayout extends FrameLayout
}
}
+ @Nullable
+ private View getNewImageMessage() {
+ if (mIsCollapsed && !mGroups.isEmpty()) {
+ // When collapsed, we're displaying the image message in a dedicated container
+ // on the right of the layout instead of inline. Let's add the isolated image there
+ MessagingGroup messagingGroup = mGroups.getLast();
+ MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ return isolatedMessage.getView();
+ }
+ }
+ return null;
+ }
+
private void removeGroups(ArrayList<MessagingGroup> oldGroups) {
int size = oldGroups.size();
for (int i = 0; i < size; i++) {
@@ -417,22 +445,44 @@ public class MessagingLayout extends FrameLayout
return mPeopleHelper.createAvatarSymbol(senderName, symbol, layoutColor);
}
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setLayoutColorAsync")
public void setLayoutColor(int color) {
mLayoutColor = color;
}
- @RemotableViewMethod
+ /**
+ * @hide
+ */
+ public Runnable setLayoutColorAsync(int color) {
+ mLayoutColor = color;
+ return () -> {};
+ }
+
+ @RemotableViewMethod(asyncImpl = "setIsOneToOneAsync")
public void setIsOneToOne(boolean oneToOne) {
mIsOneToOne = oneToOne;
}
- @RemotableViewMethod
+ /**
+ * @hide
+ */
+ public Runnable setIsOneToOneAsync(boolean oneToOne) {
+ mIsOneToOne = oneToOne;
+ return () -> {};
+ }
+
+ @RemotableViewMethod(asyncImpl = "setSenderTextColorAsync")
public void setSenderTextColor(int color) {
mSenderTextColor = color;
}
-
+ /**
+ * @hide
+ */
+ public Runnable setSenderTextColorAsync(int color) {
+ mSenderTextColor = color;
+ return () -> {};
+ }
/**
* @param color the color of the notification background
*/
@@ -441,11 +491,19 @@ public class MessagingLayout extends FrameLayout
// Nothing to do with this
}
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setMessageTextColorAsync")
public void setMessageTextColor(int color) {
mMessageTextColor = color;
}
+ /**
+ * @hide
+ */
+ public Runnable setMessageTextColorAsync(int color) {
+ mMessageTextColor = color;
+ return () -> {};
+ }
+
public void setUser(Person user) {
mUser = user;
if (mUser.getIcon() == null) {
diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp
index 12585d5f8137..6267522f114f 100644
--- a/core/jni/android_app_PropertyInvalidatedCache.cpp
+++ b/core/jni/android_app_PropertyInvalidatedCache.cpp
@@ -35,7 +35,7 @@ int NonceStore::getMaxNonce() const {
return kMaxNonce;
}
-size_t NonceStore::getMaxByte() const {
+int32_t NonceStore::getMaxByte() const {
return kMaxByte;
}
@@ -68,13 +68,13 @@ int32_t NonceStore::getHash() const {
}
// Copy the byte block to the target and return the current hash.
-int32_t NonceStore::getByteBlock(block_t* block, size_t len) const {
+int32_t NonceStore::getByteBlock(block_t* block, int32_t len) const {
memcpy(block, (void*) byteBlock(), std::min(kMaxByte, len));
return mByteHash;
}
// Set the byte block and the hash.
-void NonceStore::setByteBlock(int hash, const block_t* block, size_t len) {
+void NonceStore::setByteBlock(int hash, const block_t* block, int32_t len) {
memcpy((void*) byteBlock(), block, len = std::min(kMaxByte, len));
mByteHash = hash;
}
diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h
index 54a4ac65fce2..1d75182356c6 100644
--- a/core/jni/android_app_PropertyInvalidatedCache.h
+++ b/core/jni/android_app_PropertyInvalidatedCache.h
@@ -27,8 +27,12 @@ namespace android::app::PropertyInvalidatedCache {
* location. Fields with a variable location are found via offsets. The offsets make this
* object position-independent, which is required because it is in shared memory and would be
* mapped into different virtual addresses for different processes.
+ *
+ * This structure is shared between 64-bit and 32-bit processes. Therefore it is imperative
+ * that the structure not use any datatypes that are architecture-dependent (like size_t).
+ * Additionally, care must be taken to avoid unexpected padding in the structure.
*/
-class NonceStore {
+class alignas(8) NonceStore {
protected:
// A convenient typedef. The jbyteArray element type is jbyte, which the compiler treats as
// signed char.
@@ -43,23 +47,27 @@ class NonceStore {
// The value of an unset field.
static constexpr int UNSET = 0;
- // The size of the nonce array.
+ // The size of the nonce array. This and the following sizes are int32_t to
+ // be ABI independent.
const int32_t kMaxNonce;
// The size of the byte array.
- const size_t kMaxByte;
+ const int32_t kMaxByte;
// The offset to the nonce array.
- const size_t mNonceOffset;
+ const int32_t mNonceOffset;
// The offset to the byte array.
- const size_t mByteOffset;
+ const int32_t mByteOffset;
// The byte block hash. This is fixed and at a known offset, so leave it in the base class.
volatile std::atomic<int32_t> mByteHash;
+ // A 4-byte padd that makes the size of this structure a multiple of 8 bytes.
+ const int32_t _pad = 0;
+
// The constructor is protected! It only makes sense when called from a subclass.
- NonceStore(int kMaxNonce, size_t kMaxByte, volatile nonce_t* nonce, volatile block_t* block) :
+ NonceStore(int kMaxNonce, int kMaxByte, volatile nonce_t* nonce, volatile block_t* block) :
kMaxNonce(kMaxNonce),
kMaxByte(kMaxByte),
mNonceOffset(offset(this, const_cast<nonce_t*>(nonce))),
@@ -70,7 +78,7 @@ class NonceStore {
// These provide run-time access to the sizing parameters.
int getMaxNonce() const;
- size_t getMaxByte() const;
+ int getMaxByte() const;
// Fetch a nonce, returning UNSET if the index is out of range. This method specifically
// does not throw or generate an error if the index is out of range; this allows the method
@@ -86,10 +94,10 @@ class NonceStore {
int32_t getHash() const;
// Copy the byte block to the target and return the current hash.
- int32_t getByteBlock(block_t* block, size_t len) const;
+ int32_t getByteBlock(block_t* block, int32_t len) const;
// Set the byte block and the hash.
- void setByteBlock(int hash, const block_t* block, size_t len);
+ void setByteBlock(int hash, const block_t* block, int32_t len);
private:
@@ -113,6 +121,12 @@ class NonceStore {
}
};
+// Assert that the size of the object is fixed, independent of the CPU architecture. There are
+// four int32_t fields and one atomic<int32_t>, which sums to 20 bytes total. This assertion
+// uses a constant instead of computing the size of the objects in the compiler, to avoid
+// different answers on different architectures.
+static_assert(sizeof(NonceStore) == 24);
+
/**
* A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes. The
* byte array has an associated hash. This class provides methods to read and write the fields
@@ -126,20 +140,22 @@ class NonceStore {
* The template is parameterized by the number of nonces it supports and the number of bytes in
* the string block.
*/
-template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore {
+template<int MAX_NONCE, int MAX_BYTE> class CacheNonce : public NonceStore {
// The array of nonces
- volatile nonce_t mNonce[maxNonce];
+ volatile nonce_t mNonce[MAX_NONCE];
// The byte array. This is not atomic but it is guarded by the mByteHash.
- volatile block_t mByteBlock[maxByte];
+ volatile block_t mByteBlock[MAX_BYTE];
public:
+ // Export the parameters for use in compiler assertions.
+ static constexpr int kMaxNonceCount = MAX_NONCE;
+ static constexpr int kMaxByteCount = MAX_BYTE;
+
// Construct and initialize the memory.
- CacheNonce() :
- NonceStore(maxNonce, maxByte, &mNonce[0], &mByteBlock[0])
- {
- for (int i = 0; i < maxNonce; i++) {
+ CacheNonce() : NonceStore(MAX_NONCE, MAX_BYTE, &mNonce[0], &mByteBlock[0]) {
+ for (int i = 0; i < MAX_NONCE; i++) {
mNonce[i] = UNSET;
}
mByteHash = UNSET;
@@ -155,4 +171,10 @@ template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore {
typedef CacheNonce</* max nonce */ 128, /* byte block size */ 8192> SystemCacheNonce;
// LINT.ThenChange(/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java:system_nonce_config)
+// Verify that there is no padding in the final class.
+static_assert(sizeof(SystemCacheNonce) ==
+ sizeof(NonceStore)
+ + SystemCacheNonce::kMaxNonceCount*8
+ + SystemCacheNonce::kMaxByteCount);
+
} // namespace android.app.PropertyInvalidatedCache
diff --git a/core/jni/android_os_PerfettoTrackEventExtra.cpp b/core/jni/android_os_PerfettoTrackEventExtra.cpp
index b8bdc8c29199..18f05cabd12d 100644
--- a/core/jni/android_os_PerfettoTrackEventExtra.cpp
+++ b/core/jni/android_os_PerfettoTrackEventExtra.cpp
@@ -23,7 +23,8 @@
#include <nativehelper/utils.h>
#include <tracing_sdk.h>
-static constexpr ssize_t kMaxStrLen = 4096;
+#include <list>
+
namespace android {
template <typename T>
inline static T* toPointer(jlong ptr) {
@@ -35,24 +36,145 @@ inline static jlong toJLong(T* ptr) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr));
}
+/**
+ * @brief A thread-safe utility class for converting Java UTF-16 strings to ASCII in JNI
+ * environment.
+ *
+ * StringBuffer provides efficient conversion of Java strings to ASCII with optimized memory
+ * handling.
+ * It uses a two-tiered buffering strategy:
+ * 1. A fast path using pre-allocated thread-local buffers for strings up to 128 characters
+ * 2. A fallback path using dynamic allocation for longer strings
+ *
+ * Non-ASCII characters (>255) are replaced with '?' during conversion. The class maintains
+ * thread safety through thread-local storage and provides zero-copy string views for optimal
+ * performance.
+ *
+ * Memory Management:
+ * - Uses fixed-size thread-local buffers for both UTF-16 and ASCII characters
+ * - Overflow strings are stored in a thread-local list to maintain valid string views
+ * - Avoids unnecessary allocations in the common case of small strings
+ *
+ * Usage example:
+ * @code
+ * JNIEnv* env = ...;
+ * jstring java_string = ...;
+ * std::string_view ascii = StringBuffer::utf16_to_ascii(env, java_string);
+ * // Use the ASCII string...
+ * StringBuffer::reset(); // Clean up when done
+ * @endcode
+ *
+ * Thread Safety: All methods are thread-safe due to thread-local storage.
+ */
+class StringBuffer {
+private:
+ static constexpr size_t BASE_SIZE = 128;
+ // Temporarily stores the UTF-16 characters retrieved from the Java
+ // string before they are converted to ASCII.
+ static thread_local inline char char_buffer[BASE_SIZE];
+ // For fast-path conversions when the resulting ASCII string fits within
+ // the pre-allocated space. All ascii strings in a trace event will be stored
+ // here until emitted.
+ static thread_local inline jchar jchar_buffer[BASE_SIZE];
+ // When the fast-path conversion is not possible (because char_buffer
+ // doesn't have enough space), the converted ASCII string is stored
+ // in this list. We use list here to avoid moving the strings on resize
+ // with vector. This way, we can give out string_views from the stored strings.
+ // The additional overhead from list node allocations is fine cos we are already
+ // in an extremely unlikely path here and there are other bigger problems if here.
+ static thread_local inline std::list<std::string> overflow_strings;
+ // current offset into the char_buffer.
+ static thread_local inline size_t current_offset{0};
+ // This allows us avoid touching the overflow_strings directly in the fast path.
+ // Touching it causes some thread local init routine to run which shows up in profiles.
+ static thread_local inline bool is_overflow_strings_empty = true;
+
+ static void copy_utf16_to_ascii(const jchar* src, size_t len, char* dst, JNIEnv* env,
+ jstring str) {
+ std::transform(src, src + len, dst,
+ [](jchar c) { return (c <= 0xFF) ? static_cast<char>(c) : '?'; });
+
+ if (src != jchar_buffer) {
+ // We hit the slow path to populate src, so we have to release.
+ env->ReleaseStringCritical(str, src);
+ }
+ }
+
+public:
+ static void reset() {
+ if (!is_overflow_strings_empty) {
+ overflow_strings.clear();
+ is_overflow_strings_empty = true;
+ }
+ current_offset = 0;
+ }
+
+ // Converts a Java string (jstring) to an ASCII string_view. Characters
+ // outside the ASCII range (0-255) are replaced with '?'.
+ //
+ // @param env The JNI environment.
+ // @param val The Java string to convert.
+ // @return A string_view representing the ASCII version of the string.
+ // Returns an empty string_view if the input is null or empty.
+ static std::string_view utf16_to_ascii(JNIEnv* env, jstring val) {
+ if (!val) return "";
+
+ const jsize len = env->GetStringLength(val);
+ if (len == 0) return "";
+
+ const jchar* temp_buffer;
+
+ // Fast path: Enough space in jchar_buffer
+ if (static_cast<size_t>(len) <= BASE_SIZE) {
+ env->GetStringRegion(val, 0, len, jchar_buffer);
+ temp_buffer = jchar_buffer;
+ } else {
+ // Slow path: Fallback to asking ART for the string which will likely
+ // allocate and return a copy.
+ temp_buffer = env->GetStringCritical(val, nullptr);
+ }
+
+ const size_t next_offset = current_offset + len + 1;
+ // Fast path: Enough space in char_buffer
+ if (BASE_SIZE > next_offset) {
+ const size_t start_offset = current_offset;
+
+ copy_utf16_to_ascii(temp_buffer, len, char_buffer + current_offset, env, val);
+ char_buffer[current_offset + len] = '\0';
+
+ auto res = std::string_view(char_buffer + current_offset, len);
+ current_offset = next_offset;
+ return res;
+ } else {
+ // Slow path: Not enough space in char_buffer. Use overflow_strings.
+ // This will cause a string alloc but should be very unlikely to hit.
+ std::string& str = overflow_strings.emplace_back(len + 1, '\0');
+
+ copy_utf16_to_ascii(temp_buffer, len, str.data(), env, val);
+ is_overflow_strings_empty = false;
+ return std::string_view(str);
+ }
+ }
+};
+
static jlong android_os_PerfettoTrackEventExtraArgInt64_init(JNIEnv* env, jclass, jstring name) {
- ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
- return toJLong(new tracing_perfetto::DebugArg<int64_t>(name_chars.c_str()));
+ return toJLong(new tracing_perfetto::DebugArg<int64_t>(
+ StringBuffer::utf16_to_ascii(env, name).data()));
}
static jlong android_os_PerfettoTrackEventExtraArgBool_init(JNIEnv* env, jclass, jstring name) {
- ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
- return toJLong(new tracing_perfetto::DebugArg<bool>(name_chars.c_str()));
+ return toJLong(
+ new tracing_perfetto::DebugArg<bool>(StringBuffer::utf16_to_ascii(env, name).data()));
}
static jlong android_os_PerfettoTrackEventExtraArgDouble_init(JNIEnv* env, jclass, jstring name) {
- ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
- return toJLong(new tracing_perfetto::DebugArg<double>(name_chars.c_str()));
+ return toJLong(
+ new tracing_perfetto::DebugArg<double>(StringBuffer::utf16_to_ascii(env, name).data()));
}
static jlong android_os_PerfettoTrackEventExtraArgString_init(JNIEnv* env, jclass, jstring name) {
- ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
- return toJLong(new tracing_perfetto::DebugArg<const char*>(name_chars.c_str()));
+ return toJLong(new tracing_perfetto::DebugArg<const char*>(
+ StringBuffer::utf16_to_ascii(env, name).data()));
}
static jlong android_os_PerfettoTrackEventExtraArgInt64_delete() {
@@ -109,11 +231,9 @@ static void android_os_PerfettoTrackEventExtraArgDouble_set_value(jlong ptr, jdo
static void android_os_PerfettoTrackEventExtraArgString_set_value(JNIEnv* env, jclass, jlong ptr,
jstring val) {
- ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);
-
tracing_perfetto::DebugArg<const char*>* arg =
toPointer<tracing_perfetto::DebugArg<const char*>>(ptr);
- arg->set_value(strdup(val_chars.c_str()));
+ arg->set_value(StringBuffer::utf16_to_ascii(env, val).data());
}
static jlong android_os_PerfettoTrackEventExtraFieldInt64_init() {
@@ -186,11 +306,9 @@ static void android_os_PerfettoTrackEventExtraFieldDouble_set_value(jlong ptr, j
static void android_os_PerfettoTrackEventExtraFieldString_set_value(JNIEnv* env, jclass, jlong ptr,
jlong id, jstring val) {
- ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);
-
tracing_perfetto::ProtoField<const char*>* field =
toPointer<tracing_perfetto::ProtoField<const char*>>(ptr);
- field->set_value(id, strdup(val_chars.c_str()));
+ field->set_value(id, StringBuffer::utf16_to_ascii(env, val).data());
}
static void android_os_PerfettoTrackEventExtraFieldNested_add_field(jlong field_ptr,
@@ -231,8 +349,9 @@ static jlong android_os_PerfettoTrackEventExtraFlow_get_extra_ptr(jlong ptr) {
static jlong android_os_PerfettoTrackEventExtraNamedTrack_init(JNIEnv* env, jclass, jlong id,
jstring name, jlong parent_uuid) {
- ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
- return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, name_chars.c_str()));
+ return toJLong(
+ new tracing_perfetto::NamedTrack(id, parent_uuid,
+ StringBuffer::utf16_to_ascii(env, name).data()));
}
static jlong android_os_PerfettoTrackEventExtraNamedTrack_delete() {
@@ -246,9 +365,10 @@ static jlong android_os_PerfettoTrackEventExtraNamedTrack_get_extra_ptr(jlong pt
static jlong android_os_PerfettoTrackEventExtraCounterTrack_init(JNIEnv* env, jclass, jstring name,
jlong parent_uuid) {
- ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
-
- return toJLong(new tracing_perfetto::RegisteredTrack(1, parent_uuid, name_chars.c_str(), true));
+ return toJLong(
+ new tracing_perfetto::RegisteredTrack(1, parent_uuid,
+ StringBuffer::utf16_to_ascii(env, name).data(),
+ true));
}
static jlong android_os_PerfettoTrackEventExtraCounterTrack_delete() {
@@ -318,11 +438,11 @@ static void android_os_PerfettoTrackEventExtra_clear_args(jlong ptr) {
static void android_os_PerfettoTrackEventExtra_emit(JNIEnv* env, jclass, jint type, jlong cat_ptr,
jstring name, jlong extra_ptr) {
- ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name);
-
tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr);
- tracing_perfetto::trace_event(type, category->get(), name_chars.c_str(),
+ tracing_perfetto::trace_event(type, category->get(),
+ StringBuffer::utf16_to_ascii(env, name).data(),
toPointer<tracing_perfetto::Extra>(extra_ptr));
+ StringBuffer::reset();
}
static jlong android_os_PerfettoTrackEventExtraProto_init() {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b2649a471b8a..a73ff421acf7 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -424,6 +424,15 @@ static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jin
assetmanager->SetDefaultLocale(default_locale_int);
}
+static void NativeSetOverlayConstraints(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr,
+ jint displayId, jint deviceId) {
+ ATRACE_NAME("AssetManager::SetDisplayIdAndDeviceId");
+
+ auto assetmanager = LockAndStartAssetManager(ptr);
+ assetmanager->SetOverlayConstraints(static_cast<int32_t>(displayId),
+ static_cast<int32_t>(deviceId));
+}
+
static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr,
jboolean includeOverlays,
jboolean includeLoaders) {
@@ -1554,6 +1563,7 @@ static const JNINativeMethod gAssetManagerMethods[] = {
{"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;ZZ)V", (void*)NativeSetApkAssets},
{"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIIIZ)V",
(void*)NativeSetConfiguration},
+ {"nativeSetOverlayConstraints", "(JII)V", (void*)NativeSetOverlayConstraints},
{"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
(void*)NativeGetAssignedPackageIdentifiers},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b99b0ef7f24e..a8e51a7c1fe8 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -765,28 +765,28 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong
std::vector<int32_t> dimensions;
std::vector<int32_t> sizes;
std::vector<int32_t> samplingKeys;
- int32_t fd = -1;
+ base::unique_fd fd;
if (jdimensionArray) {
jsize numLuts = env->GetArrayLength(jdimensionArray);
- ScopedIntArrayRW joffsets(env, joffsetArray);
+ ScopedIntArrayRO joffsets(env, joffsetArray);
if (joffsets.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from joffsetArray");
return;
}
- ScopedIntArrayRW jdimensions(env, jdimensionArray);
+ ScopedIntArrayRO jdimensions(env, jdimensionArray);
if (jdimensions.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jdimensionArray");
return;
}
- ScopedIntArrayRW jsizes(env, jsizeArray);
+ ScopedIntArrayRO jsizes(env, jsizeArray);
if (jsizes.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsizeArray");
return;
}
- ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
+ ScopedIntArrayRO jsamplingKeys(env, jsamplingKeyArray);
if (jsamplingKeys.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsamplingKeyArray");
return;
}
@@ -796,15 +796,15 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong
sizes = std::vector<int32_t>(jsizes.get(), jsizes.get() + numLuts);
samplingKeys = std::vector<int32_t>(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
- ScopedFloatArrayRW jbuffers(env, jbufferArray);
+ ScopedFloatArrayRO jbuffers(env, jbufferArray);
if (jbuffers.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRO from jbufferArray");
return;
}
// create the shared memory and copy jbuffers
size_t bufferSize = jbuffers.size() * sizeof(float);
- fd = ashmem_create_region("lut_shared_mem", bufferSize);
+ fd.reset(ashmem_create_region("lut_shared_mem", bufferSize));
if (fd < 0) {
jniThrowRuntimeException(env, "ashmem_create_region() failed");
return;
@@ -820,7 +820,7 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong
}
}
- transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys);
+ transaction->setLuts(ctrl, std::move(fd), offsets, dimensions, sizes, samplingKeys);
}
static void nativeSetPictureProfileId(JNIEnv* env, jclass clazz, jlong transactionObj,
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 7ffe0ed7c6cd..8f36ecb8b01a 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -97,7 +97,13 @@ static void native_updateValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray
static void native_incrementValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
jlong timestamp) {
auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
- counter->incrementValue(JavaUint64Array(env, values), timestamp);
+ if (values != nullptr) {
+ counter->incrementValue(JavaUint64Array(env, values), timestamp);
+ } else {
+ // Pass an empty Uint64Array, which is equivalent to an array of zeros.
+ // This is done to ensure that the timestamp is still updated in the counter.
+ counter->incrementValue(Uint64Array(), timestamp);
+ }
}
static void native_addCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values) {
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index b51f72dee260..aa8f8419ac46 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -5,7 +5,6 @@ joeo@google.com
singhtejinder@google.com
yanmin@google.com
yaochen@google.com
-yro@google.com
zhouwenjie@google.com
# Frameworks
diff --git a/core/res/res/drawable-w192dp/loader_horizontal_watch.xml b/core/res/res/drawable-w192dp/loader_horizontal_watch.xml
deleted file mode 100644
index 18cea6e0d87d..000000000000
--- a/core/res/res/drawable-w192dp/loader_horizontal_watch.xml
+++ /dev/null
@@ -1,97 +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.
- -->
-
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="15dp" android:width="67dp" android:viewportHeight="15" android:viewportWidth="67">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="33.5" android:translateY="7.5">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M33.5 -7.5 C33.5,-7.5 33.5,7.5 33.5,7.5 C33.5,7.5 -33.5,7.5 -33.5,7.5 C-33.5,7.5 -33.5,-7.5 -33.5,-7.5 C-33.5,-7.5 33.5,-7.5 33.5,-7.5c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-296.5" android:translateY="-62.5" android:pivotX="330" android:pivotY="70" android:scaleX="0.1" android:scaleY="0.1">
- <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-224.84700000000004" android:translateY="-321.948" android:pivotX="555.09" android:pivotY="-329" android:rotation="28.9" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M194.88 359 C190,359 185.05,357.81 180.48,355.3 C59.86,289.14 -41.55,191.9 -112.79,74.11 C-186.14,-47.16 -224.91,-186.55 -224.91,-329 C-224.91,-345.57 -211.48,-359 -194.91,-359 C-178.34,-359 -164.91,-345.57 -164.91,-329 C-164.91,-197.5 -129.13,-68.84 -61.45,43.06 C4.33,151.82 97.97,241.6 209.33,302.69 C223.86,310.66 229.18,328.9 221.21,343.42 C215.75,353.37 205.48,359 194.88,359c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_5_G" android:translateX="744.323" android:translateY="-277.96299999999997" android:pivotX="-414.08" android:pivotY="-372.985" android:rotation="28.9">
- <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-335.95 402.99 C-351.13,402.99 -364.16,391.5 -365.76,376.07 C-367.46,359.59 -355.49,344.85 -339.01,343.14 C-162.93,324.91 -0.15,242.33 119.34,110.62 C239.66,-22.01 305.92,-193.76 305.92,-372.98 C305.92,-389.55 319.35,-402.98 335.92,-402.98 C352.49,-402.98 365.92,-389.55 365.92,-372.98 C365.92,-178.82 294.13,7.24 163.78,150.93 C34.34,293.61 -142.03,383.07 -332.83,402.82 C-333.88,402.93 -334.92,402.99 -335.95,402.99c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_2_G" android:translateX="185.385" android:translateY="70.09100000000001" android:pivotX="144.858" android:pivotY="-721.039" android:rotation="28.9" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M144.62 58.96 C144.61,58.96 144.61,58.96 144.6,58.96 C40.39,58.93 -60.82,38.66 -156.19,-1.28 C-171.48,-7.68 -178.68,-25.26 -172.28,-40.54 C-165.88,-55.82 -148.3,-63.02 -133.02,-56.62 C-45.02,-19.77 48.4,-1.07 144.63,-1.04 C161.19,-1.03 174.62,12.4 174.62,28.97 C174.61,45.53 161.18,58.96 144.62,58.96c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G" android:translateX="330" android:translateY="70">
- <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-660 -313 C-660,-313 -660,313 -660,313 C-660,313 660,313 660,313 C660,313 660,-313 660,-313 C660,-313 -660,-313 -660,-313c M300.74 -1.16 C205.46,38.62 103.22,59.09 -0.03,59.05 C-103.28,59.01 -205.51,38.48 -300.76,-1.37 C-316.05,-7.76 -323.26,-25.34 -316.86,-40.62 C-310.47,-55.91 -292.9,-63.12 -277.61,-56.72 C-189.68,-19.94 -95.32,-0.98 -0.01,-0.95 C95.3,-0.92 189.67,-19.81 277.63,-56.53 C292.92,-62.91 310.49,-55.69 316.87,-40.4 C323.25,-25.11 316.03,-7.54 300.74,-1.16c "/>
- </group>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.9" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.135 0.202,0.848 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.9" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.135 0.202,0.848 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.9" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.135 0.202,0.848 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
-
diff --git a/core/res/res/drawable-w204dp/loader_horizontal_watch.xml b/core/res/res/drawable-w204dp/loader_horizontal_watch.xml
deleted file mode 100644
index fbc6eab320eb..000000000000
--- a/core/res/res/drawable-w204dp/loader_horizontal_watch.xml
+++ /dev/null
@@ -1,104 +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.
- -->
-
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="15dp" android:width="70dp" android:viewportHeight="15" android:viewportWidth="70">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="35" android:translateY="7.5">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M35 -7.5 C35,-7.5 35,7.5 35,7.5 C35,7.5 -35,7.5 -35,7.5 C-35,7.5 -35,-7.5 -35,-7.5 C-35,-7.5 35,-7.5 35,-7.5c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-310" android:translateY="-64" android:pivotX="345" android:pivotY="71.5" android:scaleX="0.1" android:scaleY="0.1">
- <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-239.44799999999998" android:translateY="-341.45" android:pivotX="584.448" android:pivotY="-346.55" android:rotation="28.8" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M205.28 376.55 C200.4,376.55 195.46,375.36 190.88,372.85 C64.08,303.29 -42.54,201.07 -117.44,77.24 C-194.55,-50.25 -235.31,-196.79 -235.31,-346.55 C-235.31,-363.12 -221.88,-376.55 -205.31,-376.55 C-188.74,-376.55 -175.31,-363.12 -175.31,-346.55 C-175.31,-207.74 -137.54,-71.93 -66.1,46.19 C3.34,160.99 102.18,255.76 219.73,320.24 C234.26,328.21 239.58,346.45 231.61,360.97 C226.15,370.92 215.88,376.55 205.28,376.55c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_5_G" android:translateX="781.413" android:translateY="-295.124" android:pivotX="-436.413" android:pivotY="-392.876" android:rotation="28.8">
- <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-353.86 422.88 C-369.04,422.88 -382.07,411.4 -383.67,395.97 C-385.37,379.49 -373.4,364.74 -356.92,363.03 C-171.06,343.79 0.76,256.62 126.89,117.59 C253.89,-22.41 323.83,-203.7 323.83,-392.88 C323.83,-409.44 337.26,-422.88 353.83,-422.88 C370.4,-422.88 383.83,-409.44 383.83,-392.88 C383.83,-188.76 308.36,6.84 171.32,157.9 C35.25,307.89 -150.15,401.94 -350.74,422.72 C-351.79,422.82 -352.83,422.88 -353.86,422.88c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_2_G" android:translateX="192.671" android:translateY="71.49599999999998" android:pivotX="152.329" android:pivotY="-759.496" android:rotation="28.8" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M152.33 60.5 C152.33,60.5 152.32,60.5 152.32,60.5 C42.76,60.47 -63.64,39.16 -163.91,-2.82 C-179.19,-9.22 -186.39,-26.8 -179.99,-42.08 C-173.59,-57.36 -156.02,-64.57 -140.73,-58.16 C-47.84,-19.27 50.77,0.47 152.34,0.5 C168.91,0.51 182.33,13.94 182.33,30.51 C182.32,47.08 168.89,60.5 152.33,60.5c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G" android:translateX="345" android:translateY="71.5">
- <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-579 -259.5 C-579,-259.5 -579,259.5 -579,259.5 C-579,259.5 579,259.5 579,259.5 C579,259.5 579,-259.5 579,-259.5 C579,-259.5 -579,-259.5 -579,-259.5c M316.17 -2.8 C216,39.02 108.52,60.54 -0.03,60.5 C-108.58,60.46 -216.04,38.87 -316.18,-3.02 C-331.47,-9.41 -338.68,-26.99 -332.28,-42.27 C-325.89,-57.56 -308.32,-64.76 -293.03,-58.37 C-200.22,-19.55 -100.61,0.46 -0.01,0.5 C100.6,0.54 200.22,-19.41 293.06,-58.17 C308.35,-64.55 325.92,-57.33 332.3,-42.04 C338.68,-26.75 331.46,-9.18 316.17,-2.8c "/>
- </group>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
-
diff --git a/core/res/res/drawable-w216dp/loader_horizontal_watch.xml b/core/res/res/drawable-w216dp/loader_horizontal_watch.xml
deleted file mode 100644
index ed4b7ea0ff02..000000000000
--- a/core/res/res/drawable-w216dp/loader_horizontal_watch.xml
+++ /dev/null
@@ -1,105 +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.
- -->
-
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="16dp" android:width="74dp" android:viewportHeight="16" android:viewportWidth="74">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="37" android:translateY="8">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M37 -8 C37,-8 37,8 37,8 C37,8 -37,8 -37,8 C-37,8 -37,-8 -37,-8 C-37,-8 37,-8 37,-8c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-328" android:translateY="-65.5" android:pivotX="365" android:pivotY="73.5" android:scaleX="0.1" android:scaleY="0.1">
- <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-256.447" android:translateY="-365.014" android:pivotX="621.447" android:pivotY="-368.486" android:rotation="28.8" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M218.28 398.49 C213.4,398.49 208.46,397.3 203.88,394.78 C69.34,320.99 -43.78,212.53 -123.25,81.15 C-205.06,-54.11 -248.31,-209.59 -248.31,-368.49 C-248.31,-385.05 -234.88,-398.49 -218.31,-398.49 C-201.74,-398.49 -188.31,-385.05 -188.31,-368.49 C-188.31,-220.54 -148.06,-75.8 -71.91,50.09 C2.1,172.45 107.45,273.45 232.73,342.18 C247.26,350.15 252.58,368.38 244.61,382.91 C239.15,392.86 228.88,398.49 218.28,398.49c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_5_G" android:translateX="829.0260000000001" android:translateY="-315.759" android:pivotX="-464.026" android:pivotY="-417.741" android:rotation="28.8">
- <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-376.25 447.74 C-391.43,447.74 -404.46,436.26 -406.05,420.83 C-407.76,404.35 -395.78,389.61 -379.3,387.9 C-181.22,367.38 1.9,274.48 136.32,126.3 C271.67,-22.9 346.22,-216.12 346.22,-417.74 C346.22,-434.31 359.65,-447.74 376.22,-447.74 C392.79,-447.74 406.22,-434.31 406.22,-417.74 C406.22,-201.18 326.15,6.35 180.76,166.61 C36.39,325.75 -160.31,425.54 -373.12,447.58 C-374.17,447.69 -375.22,447.74 -376.25,447.74c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_2_G" android:translateX="203.029" android:translateY="74.06899999999996" android:pivotX="161.971" android:pivotY="-807.569" android:rotation="28.8" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M161.97 62.43 C161.97,62.43 161.96,62.43 161.96,62.43 C45.71,62.4 -67.17,39.79 -173.55,-4.75 C-188.83,-11.15 -196.03,-28.72 -189.63,-44.01 C-183.24,-59.29 -165.66,-66.49 -150.38,-60.09 C-51.37,-18.64 53.72,2.4 161.98,2.43 C178.55,2.44 191.98,15.87 191.97,32.44 C191.97,49 178.54,62.43 161.97,62.43c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G" android:translateX="365" android:translateY="73.5">
- <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-609 -244.5 C-609,-244.5 -609,244.5 -609,244.5 C-609,244.5 609,244.5 609,244.5 C609,244.5 609,-244.5 609,-244.5 C609,-244.5 -609,-244.5 -609,-244.5c M335.44 -4.16 C229.17,40.21 115.13,63.04 -0.04,63 C-115.21,62.96 -229.22,40.05 -335.47,-4.39 C-350.76,-10.79 -357.95,-28.36 -351.56,-43.65 C-345.17,-58.93 -327.59,-66.14 -312.31,-59.74 C-213.39,-18.36 -107.24,2.96 -0.02,3 C107.21,3.04 213.38,-18.22 312.33,-59.53 C327.62,-65.91 345.19,-58.69 351.57,-43.4 C357.95,-28.11 350.73,-10.54 335.44,-4.16c "/>
- </group>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.3" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.3" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.8" android:valueTo="-51.3" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
-
-
diff --git a/core/res/res/drawable-w228dp/loader_horizontal_watch.xml b/core/res/res/drawable-w228dp/loader_horizontal_watch.xml
deleted file mode 100644
index 6b86c634d554..000000000000
--- a/core/res/res/drawable-w228dp/loader_horizontal_watch.xml
+++ /dev/null
@@ -1,103 +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.
- -->
-
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="14dp" android:width="76dp" android:viewportHeight="14" android:viewportWidth="76">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="39" android:translateY="8">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M39 -8 C39,-8 39,8 39,8 C39,8 -39,8 -39,8 C-39,8 -39,-8 -39,-8 C-39,-8 39,-8 39,-8c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-345" android:translateY="-67" android:pivotX="384" android:pivotY="75" android:scaleX="0.1" android:scaleY="0.1">
- <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-274.19" android:translateY="-390.077" android:pivotX="658.448" android:pivotY="-390.423" android:rotation="28.7" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M231.28 420.42 C226.4,420.42 221.45,419.23 216.88,416.72 C74.61,338.68 -45.02,224 -129.06,85.06 C-171.54,14.82 -204.38,-60.73 -226.66,-139.5 C-249.65,-220.76 -261.31,-305.18 -261.31,-390.42 C-261.31,-406.99 -247.88,-420.42 -231.31,-420.42 C-214.74,-420.42 -201.31,-406.99 -201.31,-390.42 C-201.31,-310.71 -190.42,-231.78 -168.93,-155.83 C-148.11,-82.23 -117.42,-11.63 -77.72,54 C0.86,183.92 112.71,291.15 245.73,364.12 C260.26,372.08 265.58,390.32 257.61,404.85 C252.15,414.79 241.88,420.42 231.28,420.42c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_5_G" android:translateX="875.8979999999999" android:translateY="-337.894" android:pivotX="-491.64" android:pivotY="-442.606" android:rotation="28.7">
- <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-398.64 472.61 C-413.82,472.61 -426.84,461.13 -428.44,445.7 C-430.15,429.22 -418.17,414.47 -401.69,412.77 C-191.38,390.98 3.04,292.33 145.75,135.01 C289.46,-23.4 368.6,-228.54 368.6,-442.61 C368.6,-459.17 382.04,-472.61 398.6,-472.61 C415.17,-472.61 428.6,-459.17 428.6,-442.61 C428.6,-213.6 343.93,5.85 190.19,175.33 C37.53,343.61 -170.48,449.13 -395.51,472.44 C-396.56,472.55 -397.6,472.61 -398.64,472.61c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_2_G" android:translateX="212.64499999999998" android:translateY="75.14200000000005" android:pivotX="171.613" android:pivotY="-855.642" android:rotation="28.7" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M171.61 64.36 C171.61,64.36 171.61,64.36 171.61,64.36 C48.68,64.32 -70.7,40.42 -183.19,-6.68 C-198.47,-13.07 -205.68,-30.65 -199.28,-45.93 C-192.88,-61.22 -175.3,-68.42 -160.02,-62.02 C-54.9,-18.01 56.68,4.33 171.62,4.36 C188.19,4.36 201.62,17.8 201.61,34.36 C201.61,50.93 188.18,64.36 171.61,64.36c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G" android:translateX="384" android:translateY="75">
- <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-611 -259 C-611,-259 -611,259 -611,259 C-611,259 611,259 611,259 C611,259 611,-259 611,-259 C611,-259 -611,-259 -611,-259c M354.66 -6.52 C242.36,40.4 121.76,64.54 -0.04,64.5 C-121.84,64.46 -242.44,40.23 -354.74,-6.76 C-370.04,-13.16 -377.24,-30.73 -370.84,-46.02 C-364.44,-61.3 -346.94,-68.51 -331.64,-62.12 C-226.54,-18.18 -113.84,4.46 -0.04,4.5 C113.76,4.54 226.56,-18.02 331.56,-61.89 C346.86,-68.27 364.46,-61.05 370.86,-45.76 C377.26,-30.47 369.96,-12.9 354.66,-6.52c "/>
- </group>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
diff --git a/core/res/res/drawable-w240dp/loader_horizontal_watch.xml b/core/res/res/drawable-w240dp/loader_horizontal_watch.xml
deleted file mode 100644
index ad60bbdc420c..000000000000
--- a/core/res/res/drawable-w240dp/loader_horizontal_watch.xml
+++ /dev/null
@@ -1,104 +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.
- -->
-
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="17dp" android:width="82dp" android:viewportHeight="17" android:viewportWidth="82">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="41" android:translateY="8.5">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M41 -8.5 C41,-8.5 41,8.5 41,8.5 C41,8.5 -41,8.5 -41,8.5 C-41,8.5 -41,-8.5 -41,-8.5 C-41,-8.5 41,-8.5 41,-8.5c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-362.5" android:translateY="-69" android:pivotX="403.5" android:pivotY="77.5" android:scaleX="0.1" android:scaleY="0.1">
- <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-291.64799999999997" android:translateY="-414.141" android:pivotX="695.448" android:pivotY="-412.359" android:rotation="28.7" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M244.28 442.36 C239.4,442.36 234.45,441.17 229.88,438.66 C79.87,356.38 -46.26,235.46 -134.87,88.96 C-179.66,14.91 -214.28,-64.74 -237.78,-147.79 C-262.02,-233.47 -274.31,-322.49 -274.31,-412.36 C-274.31,-428.93 -260.88,-442.36 -244.31,-442.36 C-227.74,-442.36 -214.31,-428.93 -214.31,-412.36 C-214.31,-328.01 -202.78,-244.49 -180.05,-164.12 C-158.01,-86.24 -125.54,-11.54 -83.53,57.91 C-0.38,195.38 117.97,308.85 258.73,386.05 C273.26,394.02 278.58,412.26 270.61,426.78 C265.15,436.73 254.88,442.36 244.28,442.36c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_5_G" android:translateX="923.0530000000001" android:translateY="-359.029" android:pivotX="-519.253" android:pivotY="-467.471" android:rotation="28.7">
- <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-421.02 497.47 C-436.2,497.47 -449.23,485.99 -450.83,470.56 C-452.53,454.08 -440.56,439.34 -424.08,437.63 C-201.54,414.57 4.18,310.19 155.19,143.73 C229.54,61.77 287.7,-31.75 328.04,-134.22 C369.81,-240.3 390.99,-352.42 390.99,-467.47 C390.99,-484.04 404.42,-497.47 420.99,-497.47 C437.56,-497.47 450.99,-484.04 450.99,-467.47 C450.99,-226.02 361.72,5.35 199.63,184.04 C38.67,361.47 -180.63,472.73 -417.89,497.31 C-418.94,497.42 -419.99,497.47 -421.02,497.47c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_2_G" android:translateX="222.54600000000002" android:translateY="77.21400000000006" android:pivotX="181.254" android:pivotY="-903.714" android:rotation="28.7" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M181.26 66.28 C181.25,66.28 181.25,66.28 181.25,66.28 C51.64,66.25 -74.22,41.06 -192.83,-8.6 C-208.12,-15 -215.32,-32.58 -208.92,-47.86 C-202.52,-63.15 -184.94,-70.35 -169.66,-63.95 C-58.42,-17.38 59.64,6.25 181.26,6.28 C197.83,6.29 211.26,19.72 211.26,36.29 C211.25,52.86 197.82,66.28 181.26,66.28c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G" android:translateX="403.5" android:translateY="77.5">
- <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-630.5 -255.5 C-630.5,-255.5 -630.5,255.5 -630.5,255.5 C-630.5,255.5 630.5,255.5 630.5,255.5 C630.5,255.5 630.5,-255.5 630.5,-255.5 C630.5,-255.5 -630.5,-255.5 -630.5,-255.5c M374 -8.88 C255.5,40.59 128.4,66.04 0,66 C-128.4,65.95 -255.6,40.42 -374,-9.14 C-389.3,-15.53 -396.5,-33.11 -390.1,-48.39 C-383.7,-63.68 -366.2,-70.88 -350.9,-64.49 C-239.7,-18 -120.5,5.96 0,6 C120.4,6.04 239.7,-17.84 350.9,-64.25 C366.2,-70.63 383.7,-63.41 390.1,-48.12 C396.5,-32.83 389.3,-15.26 374,-8.88c "/>
- </group>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
-
diff --git a/core/res/res/drawable/loader_horizontal_watch.xml b/core/res/res/drawable/loader_horizontal_watch.xml
deleted file mode 100644
index 6b86c634d554..000000000000
--- a/core/res/res/drawable/loader_horizontal_watch.xml
+++ /dev/null
@@ -1,103 +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.
- -->
-
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="14dp" android:width="76dp" android:viewportHeight="14" android:viewportWidth="76">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="39" android:translateY="8">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M39 -8 C39,-8 39,8 39,8 C39,8 -39,8 -39,8 C-39,8 -39,-8 -39,-8 C-39,-8 39,-8 39,-8c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-345" android:translateY="-67" android:pivotX="384" android:pivotY="75" android:scaleX="0.1" android:scaleY="0.1">
- <group android:name="_R_G_L_0_G_L_6_G" android:translateX="-274.19" android:translateY="-390.077" android:pivotX="658.448" android:pivotY="-390.423" android:rotation="28.7" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_6_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M231.28 420.42 C226.4,420.42 221.45,419.23 216.88,416.72 C74.61,338.68 -45.02,224 -129.06,85.06 C-171.54,14.82 -204.38,-60.73 -226.66,-139.5 C-249.65,-220.76 -261.31,-305.18 -261.31,-390.42 C-261.31,-406.99 -247.88,-420.42 -231.31,-420.42 C-214.74,-420.42 -201.31,-406.99 -201.31,-390.42 C-201.31,-310.71 -190.42,-231.78 -168.93,-155.83 C-148.11,-82.23 -117.42,-11.63 -77.72,54 C0.86,183.92 112.71,291.15 245.73,364.12 C260.26,372.08 265.58,390.32 257.61,404.85 C252.15,414.79 241.88,420.42 231.28,420.42c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_5_G" android:translateX="875.8979999999999" android:translateY="-337.894" android:pivotX="-491.64" android:pivotY="-442.606" android:rotation="28.7">
- <path android:name="_R_G_L_0_G_L_5_G_D_0_P_0" android:fillColor="#303030" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-398.64 472.61 C-413.82,472.61 -426.84,461.13 -428.44,445.7 C-430.15,429.22 -418.17,414.47 -401.69,412.77 C-191.38,390.98 3.04,292.33 145.75,135.01 C289.46,-23.4 368.6,-228.54 368.6,-442.61 C368.6,-459.17 382.04,-472.61 398.6,-472.61 C415.17,-472.61 428.6,-459.17 428.6,-442.61 C428.6,-213.6 343.93,5.85 190.19,175.33 C37.53,343.61 -170.48,449.13 -395.51,472.44 C-396.56,472.55 -397.6,472.61 -398.64,472.61c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_2_G" android:translateX="212.64499999999998" android:translateY="75.14200000000005" android:pivotX="171.613" android:pivotY="-855.642" android:rotation="28.7" android:scaleY="0">
- <path android:name="_R_G_L_0_G_L_2_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M171.61 64.36 C171.61,64.36 171.61,64.36 171.61,64.36 C48.68,64.32 -70.7,40.42 -183.19,-6.68 C-198.47,-13.07 -205.68,-30.65 -199.28,-45.93 C-192.88,-61.22 -175.3,-68.42 -160.02,-62.02 C-54.9,-18.01 56.68,4.33 171.62,4.36 C188.19,4.36 201.62,17.8 201.61,34.36 C201.61,50.93 188.18,64.36 171.61,64.36c "/>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G" android:translateX="384" android:translateY="75">
- <path android:name="_R_G_L_0_G_L_0_G_D_0_P_0" android:fillColor="#000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-611 -259 C-611,-259 -611,259 -611,259 C-611,259 611,259 611,259 C611,259 611,-259 611,-259 C611,-259 -611,-259 -611,-259c M354.66 -6.52 C242.36,40.4 121.76,64.54 -0.04,64.5 C-121.84,64.46 -242.44,40.23 -354.74,-6.76 C-370.04,-13.16 -377.24,-30.73 -370.84,-46.02 C-364.44,-61.3 -346.94,-68.51 -331.64,-62.12 C-226.54,-18.18 -113.84,4.46 -0.04,4.5 C113.76,4.54 226.56,-18.02 331.56,-61.89 C346.86,-68.27 364.46,-61.05 370.86,-45.76 C377.26,-30.47 369.96,-12.9 354.66,-6.52c "/>
- </group>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="333" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="rotation" android:duration="983" android:startOffset="0" android:valueFrom="28.7" android:valueTo="-51.4" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.402,0.136 0.202,0.847 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="scaleY" android:duration="0" android:startOffset="133" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:repeatCount="infinite" android:propertyName="translateX" android:duration="1000" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
diff --git a/core/res/res/drawable/progress_ring_watch.xml b/core/res/res/drawable/progress_ring_watch.xml
index 8250ee600a8f..2e65ff13b3de 100644
--- a/core/res/res/drawable/progress_ring_watch.xml
+++ b/core/res/res/drawable/progress_ring_watch.xml
@@ -16,27 +16,27 @@
-->
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
- android:fromDegrees = "270"
- android:toDegrees="270"
- android:pivotX="50%"
- android:pivotY="50%" >
+ android:fromDegrees="270"
+ android:toDegrees="270">
<layer-list>
<item>
<shape
- android:shape="ring"
- android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio"
+ android:shape="arc"
+ android:useLevel="false"
android:thickness="@dimen/progressbar_thickness"
- android:useLevel="false">
- <solid android:color="@color/materialColorSurfaceContainer"/>
+ android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio"
+ android:strokeCap="round">
+ <solid android:color="@*android:color/materialColorSurfaceContainer"/>
</shape>
</item>
<item>
<shape
- android:shape="ring"
- android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio"
+ android:shape="arc"
+ android:useLevel="true"
android:thickness="@dimen/progressbar_thickness"
- android:useLevel="true">
- <solid android:color="@color/materialColorPrimary"/>
+ android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio"
+ android:strokeCap="round">
+ <solid android:color="@*android:color/materialColorPrimary"/>
</shape>
</item>
</layer-list>
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 1bde17358825..68096f8cc50e 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2024 The Android Open Source Project
+ ~ Copyright (C) 2025 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,157 +15,74 @@
~ limitations under the License
-->
-<com.android.internal.widget.ConversationHeaderLinearLayout
+<!-- extends RelativeLayout -->
+<NotificationHeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/conversation_header"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:id="@+id/notification_header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_2025_header_height"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
android:orientation="horizontal"
- android:paddingTop="@dimen/notification_2025_margin"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no"
>
- <TextView
- android:id="@+id/conversation_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="16sp"
- android:singleLine="true"
- android:layout_weight="1"
- />
-
- <TextView
- android:id="@+id/app_name_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_2025_left_icon_size"
+ android:layout_height="@dimen/notification_2025_left_icon_size"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
android:visibility="gone"
/>
- <!-- App Name -->
- <com.android.internal.widget.ObservableTextView
- android:id="@+id/app_name_text"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:singleLine="true"
- android:visibility="gone"
- />
+ <include layout="@layout/notification_2025_conversation_icon_container" />
- <TextView
- android:id="@+id/time_divider"
+ <!-- extends ViewGroup -->
+ <NotificationTopLineView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
- android:visibility="gone"
- />
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/expand_button"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_marginVertical="@dimen/notification_2025_margin"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/notification_2025_content_margin_start"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
- <DateTimeView
- android:id="@+id/time"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:showRelative="true"
- android:singleLine="true"
- android:visibility="gone"
- />
+ <include layout="@layout/notification_2025_top_line_views" />
- <ViewStub
- android:id="@+id/chronometer"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout="@layout/notification_template_part_chronometer"
- android:visibility="gone"
- />
+ </NotificationTopLineView>
- <TextView
- android:id="@+id/verification_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
- android:visibility="gone"
- />
-
- <ImageView
- android:id="@+id/verification_icon"
- android:layout_width="@dimen/notification_verification_icon_size"
- android:layout_height="@dimen/notification_verification_icon_size"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:baseline="10dp"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_notifications_alerted"
- android:visibility="gone"
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_2025_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ android:importantForAccessibility="no"
+ android:focusable="false"
/>
- <TextView
- android:id="@+id/verification_text"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ <include layout="@layout/notification_2025_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout_weight="100"
- android:showRelative="true"
- android:singleLine="true"
- android:visibility="gone"
- />
+ android:layout_gravity="top|end"
+ android:layout_alignParentEnd="true" />
- <ImageButton
- android:id="@+id/feedback"
- android:layout_width="@dimen/notification_feedback_size"
- android:layout_height="@dimen/notification_feedback_size"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:background="?android:selectableItemBackgroundBorderless"
- android:contentDescription="@string/notification_feedback_indicator"
- android:baseline="13dp"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_feedback_indicator"
- android:visibility="gone"
- />
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true" />
- <ImageView
- android:id="@+id/phishing_alert"
- android:layout_width="@dimen/notification_2025_badge_size"
- android:layout_height="@dimen/notification_2025_badge_size"
- android:layout_marginStart="@dimen/notification_2025_badge_margin"
- android:baseline="@dimen/notification_2025_badge_baseline"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_dialog_alert_material"
- android:visibility="gone"
- android:contentDescription="@string/notification_phishing_alert_content_description"
- />
-
- <ImageView
- android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_2025_badge_size"
- android:layout_height="@dimen/notification_2025_badge_size"
- android:layout_marginStart="@dimen/notification_2025_badge_margin"
- android:baseline="@dimen/notification_2025_badge_baseline"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
-
- <ImageView
- android:id="@+id/alerted_icon"
- android:layout_width="@dimen/notification_2025_badge_size"
- android:layout_height="@dimen/notification_2025_badge_size"
- android:layout_marginStart="@dimen/notification_2025_badge_margin"
- android:baseline="@dimen/notification_2025_badge_baseline"
- android:contentDescription="@string/notification_alerted_content_description"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_notifications_alerted"
- android:visibility="gone"
- />
-</com.android.internal.widget.ConversationHeaderLinearLayout>
+</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml
index 6f3c15adb082..f1729b3c2f76 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_call.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -47,7 +47,7 @@
>
<include
- layout="@layout/notification_2025_conversation_header"
+ layout="@layout/notification_template_conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
new file mode 100644
index 000000000000..f80411103501
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
@@ -0,0 +1,216 @@
+<?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
+ -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.ConversationLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="conversation"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+
+ <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_2025_min_height"
+ android:clipChildren="false"
+ >
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_2025_left_icon_size"
+ android:layout_height="@dimen/notification_2025_left_icon_size"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ android:visibility="gone"
+ />
+
+ <include layout="@layout/notification_2025_conversation_icon_container" />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_2025_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+
+ <!--
+ NOTE: to make the expansion animation of id/notification_messaging happen vertically,
+ its X positioning must be the left edge of the notification, so instead of putting the
+ layout_marginStart on the id/notification_headerless_view_row, we put it on
+ id/notification_top_line, making the layout here just a bit different from the base.
+ -->
+ <LinearLayout
+ android:id="@+id/notification_headerless_view_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clipChildren="false"
+ >
+
+ <!--
+ NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
+ have the id/notification_headerless_view_column, as that is used for modifying
+ vertical margins to accommodate the single-line state that base supports
+ -->
+ <LinearLayout
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_2025_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_2025_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ >
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/notification_messaging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:spacing="@dimen/notification_messaging_spacing" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Images -->
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/conversation_image_message_container"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:forceHasOverlappingRendering="false"
+ android:spacing="0dp"
+ android:clipChildren="false"
+ android:visibility="gone"
+ />
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_2025_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
+ </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-20dp"
+ android:clipChildren="false"
+ android:orientation="vertical">
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end" />
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
+</LinearLayout>
+</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml
deleted file mode 100644
index 24b6ad09d3f7..000000000000
--- a/core/res/res/layout/notification_2025_template_conversation.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- extends FrameLayout -->
-<com.android.internal.widget.ConversationLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:tag="conversation"
- android:theme="@style/Theme.DeviceDefault.Notification"
- >
-
- <include layout="@layout/notification_2025_conversation_icon_container" />
-
- <!-- Wraps entire "expandable" notification -->
- <com.android.internal.widget.RemeasuringLinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="top"
- android:clipToPadding="false"
- android:clipChildren="false"
- android:orientation="vertical"
- >
- <!-- LinearLayout for Expand Button-->
- <com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/expand_button_and_content_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="start|top"
- android:orientation="horizontal"
- android:clipChildren="false"
- android:clipToPadding="false">
- <!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc-->
- <com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
- android:layout_width="0dp"
- android:orientation="vertical"
- android:layout_height="wrap_content"
- android:layout_weight="1">
-
- <!-- Header -->
-
- <!-- Use layout_marginStart instead of paddingStart to work around strange
- measurement behavior on lower display densities. -->
- <include
- layout="@layout/notification_2025_conversation_header"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="2dp"
- android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- />
-
- <!-- Messages -->
- <com.android.internal.widget.MessagingLinearLayout
- android:id="@+id/notification_messaging"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/notification_text_size"
- android:spacing="@dimen/notification_messaging_spacing"
- android:clipToPadding="false"
- android:clipChildren="false"
- />
- </com.android.internal.widget.RemeasuringLinearLayout>
-
- <!-- This is where the expand button container will be placed when collapsed-->
- </com.android.internal.widget.RemeasuringLinearLayout>
-
- <include layout="@layout/notification_template_smart_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin"
- android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end" />
- <include layout="@layout/notification_material_action_list" />
- </com.android.internal.widget.RemeasuringLinearLayout>
-
- <!--expand_button_a11y_container ensures talkback focus order is correct when view is expanded.
- The -1px of marginTop and 1px of paddingTop make sure expand_button_a11y_container is prior to
- its sibling view in accessibility focus order.
- {see android.view.ViewGroup.addChildrenForAccessibility()}
- expand_button_container will be moved under expand_button_and_content_container when collapsed,
- this dynamic movement ensures message can flow under expand button when expanded-->
- <FrameLayout
- android:id="@+id/expand_button_a11y_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="end|top"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:layout_marginTop="-1px"
- android:paddingTop="1px"
- >
- <!--expand_button_container is dynamically placed between here and at the end of the
- layout. It starts here since only FrameLayout layout params have gravity-->
- <LinearLayout
- android:id="@+id/expand_button_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="end|top"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:orientation="vertical">
- <include layout="@layout/notification_close_button"
- android:layout_width="@dimen/notification_close_button_size"
- android:layout_height="@dimen/notification_close_button_size"
- android:layout_gravity="end"
- android:layout_marginEnd="20dp"
- />
- <!--expand_button_touch_container makes sure that we can nicely center the expand
- content in the collapsed layout while the parent makes sure that we're never laid out
- bigger than the messaging content.-->
- <LinearLayout
- android:id="@+id/expand_button_touch_container"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/conversation_expand_button_height"
- android:orientation="horizontal"
- android:layout_gravity="end|top"
- android:paddingEnd="0dp"
- android:clipToPadding="false"
- android:clipChildren="false"
- >
- <!-- Images -->
- <com.android.internal.widget.MessagingLinearLayout
- android:id="@+id/conversation_image_message_container"
- android:forceHasOverlappingRendering="false"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:layout_marginStart="@dimen/conversation_image_start_margin"
- android:spacing="0dp"
- android:layout_gravity="center"
- android:clipToPadding="false"
- android:clipChildren="false"
- />
- <include layout="@layout/notification_2025_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="top|end"
- />
- </LinearLayout>
- </LinearLayout>
- </FrameLayout>
-</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml
index 0be61253c917..2114831f4c15 100644
--- a/core/res/res/layout/notification_2025_template_expanded_call.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -55,7 +55,7 @@
>
<include
- layout="@layout/notification_2025_conversation_header"
+ layout="@layout/notification_template_conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
new file mode 100644
index 000000000000..d7e8bb3b6da2
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
@@ -0,0 +1,75 @@
+<?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
+ -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.ConversationLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:tag="conversation"
+ >
+
+ <include layout="@layout/notification_2025_conversation_header"/>
+
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:clipChildren="false"
+ android:orientation="vertical">
+
+ <!-- Note: the top margin is being set in code based on the estimated space needed for
+ the header text. -->
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_weight="1"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ >
+
+ <include layout="@layout/notification_template_part_line1"/>
+
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/notification_messaging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:spacing="@dimen/notification_messaging_spacing" />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end" />
+
+ <include layout="@layout/notification_material_action_list" />
+
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <include layout="@layout/notification_template_right_icon" />
+
+</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
index 177706c6d58d..20abfee6a4b6 100644
--- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -36,17 +36,21 @@
android:clipChildren="false"
android:orientation="vertical">
+ <!-- Note: the top margin is being set in code based on the estimated space needed for
+ the header text. -->
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
- android:layout_marginTop="@dimen/notification_2025_header_height"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:orientation="vertical"
android:clipChildren="false"
>
+
+ <include layout="@layout/notification_template_part_line1"/>
+
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_2025_top_line_views.xml b/core/res/res/layout/notification_2025_top_line_views.xml
index 74873463391e..a9bde9d48dcf 100644
--- a/core/res/res/layout/notification_2025_top_line_views.xml
+++ b/core/res/res/layout/notification_2025_top_line_views.xml
@@ -20,7 +20,7 @@
<merge
xmlns:android="http://schemas.android.com/apk/res/android">
- <TextView
+ <com.android.internal.widget.ObservableTextView
android:id="@+id/app_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/core/res/res/values-w192dp/dimens_watch.xml b/core/res/res/values-w192dp/dimens_watch.xml
deleted file mode 100644
index c6bf767ab6b8..000000000000
--- a/core/res/res/values-w192dp/dimens_watch.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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.
- -->
-<resources>
- <!-- 16.7% of display size -->
- <dimen name="base_error_dialog_top_padding">32dp</dimen>
- <!-- 5.2% of display size -->
- <dimen name="base_error_dialog_padding">10dp</dimen>
- <!-- 20.83% of display size -->
- <dimen name="base_error_dialog_bottom_padding">40dp</dimen>
-
- <!-- watch's indeterminate progress bar dimens based on the current screen size -->
- <dimen name="loader_horizontal_min_width_watch">67dp</dimen>
- <dimen name="loader_horizontal_min_height_watch">15dp</dimen>
-</resources>
diff --git a/core/res/res/values-w216dp/dimens_watch.xml b/core/res/res/values-w216dp/dimens_watch.xml
deleted file mode 100644
index e14ce5e98f55..000000000000
--- a/core/res/res/values-w216dp/dimens_watch.xml
+++ /dev/null
@@ -1,21 +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.
- -->
-
-<resources>
- <!-- watch's indeterminate progress bar dimens based on the current screen size -->
- <dimen name="loader_horizontal_min_width_watch">72dp</dimen>
- <dimen name="loader_horizontal_min_height_watch">16dp</dimen>
-</resources> \ No newline at end of file
diff --git a/core/res/res/values-w228dp/dimens_watch.xml b/core/res/res/values-w228dp/dimens_watch.xml
deleted file mode 100644
index 3c6265690c3c..000000000000
--- a/core/res/res/values-w228dp/dimens_watch.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-<resources>
- <!-- watch's indeterminate progress bar dimens based on the current screen size -->
- <dimen name="loader_horizontal_min_width">76dp</dimen>
- <dimen name="loader_horizontal_min_height">14dp</dimen>
-</resources>
diff --git a/core/res/res/values-w240dp/dimens_material.xml b/core/res/res/values-w240dp/dimens_material.xml
index e30aea46aec7..bd26c8bf84df 100644
--- a/core/res/res/values-w240dp/dimens_material.xml
+++ b/core/res/res/values-w240dp/dimens_material.xml
@@ -21,8 +21,4 @@
<dimen name="screen_percentage_12">28.8dp</dimen>
<dimen name="screen_percentage_15">36dp</dimen>
<dimen name="screen_percentage_3646">87.5dp</dimen>
-
- <!-- watch's indeterminate progress bar dimens based on the current screen size -->
- <dimen name="progress_indeterminate_horizontal_min_width_watch">80dp</dimen>
- <dimen name="progress_indeterminate_horizontal_min_height_watch">17dp</dimen>
</resources>
diff --git a/core/res/res/values-watch/styles_device_defaults.xml b/core/res/res/values-watch/styles_device_defaults.xml
index eeb66e7cf6a8..fb7dbb0660c5 100644
--- a/core/res/res/values-watch/styles_device_defaults.xml
+++ b/core/res/res/values-watch/styles_device_defaults.xml
@@ -42,8 +42,5 @@
<item name="indeterminateOnly">false</item>
<!-- Use Wear Material3 ring shape as default determinate drawable -->
<item name="progressDrawable">@drawable/progress_ring_watch</item>
- <item name="indeterminateDrawable">@drawable/loader_horizontal_watch</item>
- <item name="android:minWidth">@dimen/loader_horizontal_min_width_watch</item>
- <item name="android:minHeight">@dimen/loader_horizontal_min_height_watch</item>
</style>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 66111785af4f..d2c993aecb0d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6934,6 +6934,8 @@
<enum name="line" value="2" />
<!-- Ring shape. -->
<enum name="ring" value="3" />
+ <!-- ARC shape. -->
+ <enum name="arc" value="4"/>
</attr>
<!-- Inner radius of the ring expressed as a ratio of the ring's width. For instance,
if innerRadiusRatio=9, then the inner radius equals the ring's width divided by 9.
@@ -6966,6 +6968,12 @@
<attr name="opticalInsetRight" />
<!-- Bottom optical inset. -->
<attr name="opticalInsetBottom" />
+ <!-- Attributes that customize the stroke line cap. @hide -->
+ <attr name="strokeCap" format="enum">
+ <enum name="butt" value="0"/>
+ <enum name="round" value="1"/>
+ <enum name="square" value="2"/>
+ </attr>
</declare-styleable>
<!-- Used to specify the size of the shape for GradientDrawable. -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index f2ec56c69374..59ed25a1865f 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -64,6 +64,13 @@
<integer name="auto_data_switch_performance_stability_time_threshold_millis">120000</integer>
<java-symbol type="integer" name="auto_data_switch_performance_stability_time_threshold_millis" />
+ <!-- Define the bar for switching data back to the default SIM when both SIMs are out of service
+ in milliseconds. A value of 0 means an immediate switch, otherwise for a negative value,
+ the threshold defined by auto_data_switch_availability_stability_time_threshold_millis
+ will be used instead. -->
+ <integer name="auto_data_switch_availability_switchback_stability_time_threshold_millis">150000</integer>
+ <java-symbol type="integer" name="auto_data_switch_availability_switchback_stability_time_threshold_millis" />
+
<!-- Define the maximum retry times when a validation for switching failed.-->
<integer name="auto_data_switch_validation_max_retry">7</integer>
<java-symbol type="integer" name="auto_data_switch_validation_max_retry" />
diff --git a/core/res/res/values/dimens_watch.xml b/core/res/res/values/dimens_watch.xml
index 19845916984b..7462b733b0ae 100644
--- a/core/res/res/values/dimens_watch.xml
+++ b/core/res/res/values/dimens_watch.xml
@@ -52,7 +52,7 @@
<dimen name="primary_content_alpha_device_default">0.38</dimen>
<!-- values for wear material3 progress bar(progress indicator) -->
- <item name="progressbar_inner_radius_ratio" format="float" type="dimen">2.12</item>
+ <item name="progressbar_inner_radius_ratio" format="float" type="dimen">2</item>
<dimen name="progressbar_thickness">8dp</dimen>
<dimen name="progressbar_elevation">0.1dp</dimen>
@@ -61,8 +61,4 @@
<dimen name="disabled_alpha_wear_material3">0.12</dimen>
<!-- Alpha transparency applied to elements which are considered primary (e.g. primary text) -->
<dimen name="primary_content_alpha_wear_material3">0.38</dimen>
-
- <!-- watch's indeterminate progress bar dimens -->
- <dimen name="loader_horizontal_min_width">68dp</dimen>
- <dimen name="loader_horizontal_min_height">13dp</dimen>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 26f0ab3f28e1..b013ffd41ecb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3264,6 +3264,7 @@
<java-symbol type="dimen" name="notification_content_margin" />
<java-symbol type="dimen" name="notification_2025_margin" />
<java-symbol type="dimen" name="notification_2025_content_margin_top" />
+ <java-symbol type="dimen" name="notification_2025_content_margin_start" />
<java-symbol type="dimen" name="notification_2025_expand_button_horizontal_icon_padding" />
<java-symbol type="dimen" name="notification_2025_expand_button_reduced_end_padding" />
<java-symbol type="dimen" name="notification_progress_margin_horizontal" />
@@ -4705,7 +4706,8 @@
<java-symbol type="dimen" name="conversation_icon_container_top_padding" />
<java-symbol type="dimen" name="conversation_icon_container_top_padding_small_avatar" />
<java-symbol type="layout" name="notification_template_material_conversation" />
- <java-symbol type="layout" name="notification_2025_template_conversation" />
+ <java-symbol type="layout" name="notification_2025_template_collapsed_conversation" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_conversation" />
<java-symbol type="dimen" name="button_padding_horizontal_material" />
<java-symbol type="dimen" name="button_inset_horizontal_material" />
<java-symbol type="layout" name="conversation_face_pile_layout" />
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 9383807ec761..8ef105f79988 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -56,6 +56,7 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -116,7 +117,8 @@ public class DisplayManagerGlobalTest {
@Test
public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
- ALL_DISPLAY_EVENTS, /* packageName= */ null);
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ true);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -151,7 +153,7 @@ public class DisplayManagerGlobalTest {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| INTERNAL_EVENT_FLAG_DISPLAY_STATE,
- null);
+ null, /* isEventFilterExplicit */ true);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -172,11 +174,80 @@ public class DisplayManagerGlobalTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+ public void test_refreshRateRegistration_implicitRRCallbacksEnabled()
+ throws RemoteException {
+ // Subscription without supplied events doesn't subscribe to RR events
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ // After registering to refresh rate changes, subscription without supplied events subscribe
+ // to RR events
+ mDisplayManagerGlobal.registerForRefreshRateChanges();
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+
+ // Assert all the existing listeners are also subscribed to RR events
+ CopyOnWriteArrayList<DisplayManagerGlobal.DisplayListenerDelegate> delegates =
+ mDisplayManagerGlobal.getDisplayListeners();
+ for (DisplayManagerGlobal.DisplayListenerDelegate delegate: delegates) {
+ assertEquals(ALL_DISPLAY_EVENTS | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+ delegate.mInternalEventFlagsMask);
+ }
+
+ // Subscription to RR when events are supplied doesn't happen
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ true);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ // Assert one listeners are not subscribed to RR events
+ delegates = mDisplayManagerGlobal.getDisplayListeners();
+ int subscribedListenersCount = 0;
+ int nonSubscribedListenersCount = 0;
+ for (DisplayManagerGlobal.DisplayListenerDelegate delegate: delegates) {
+
+ if (delegate.isEventFilterExplicit()) {
+ assertEquals(ALL_DISPLAY_EVENTS, delegate.mInternalEventFlagsMask);
+ nonSubscribedListenersCount++;
+ } else {
+ assertEquals(ALL_DISPLAY_EVENTS | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+ delegate.mInternalEventFlagsMask);
+ subscribedListenersCount++;
+ }
+ }
+
+ assertEquals(2, subscribedListenersCount);
+ assertEquals(1, nonSubscribedListenersCount);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+ public void test_refreshRateRegistration_implicitRRCallbacksDisabled()
+ throws RemoteException {
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+ }
+
+ @Test
public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
// First we subscribe to all events in order to test that the subsequent calls to
// registerDisplayListener will update the event mask.
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
- ALL_DISPLAY_EVENTS, /* packageName= */ null);
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ true);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -184,21 +255,24 @@ public class DisplayManagerGlobalTest {
int displayId = 1;
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
- & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null);
+ & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null,
+ /* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
Mockito.verifyZeroInteractions(mDisplayListener);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
- & ~DISPLAY_CHANGE_EVENTS, null);
+ & ~DISPLAY_CHANGE_EVENTS, null,
+ /* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
waitForHandler();
Mockito.verifyZeroInteractions(mDisplayListener);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
- & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null);
+ & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null,
+ /* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
Mockito.verifyZeroInteractions(mDisplayListener);
@@ -218,11 +292,34 @@ public class DisplayManagerGlobalTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+ public void test_registerNativeRefreshRateCallbacks_enablesRRImplicitRegistrations()
+ throws RemoteException {
+ // Registering the display listener without supplied events doesn't subscribe to RR events
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ // Native subscription for refresh rates is done
+ mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+
+ // Registering the display listener without supplied events subscribe to RR events
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+ }
+
+ @Test
public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
throws RemoteException {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
- null);
+ null, /* isEventFilterExplicit */ true);
InOrder inOrder = Mockito.inOrder(mDisplayManager);
inOrder.verify(mDisplayManager)
@@ -260,9 +357,10 @@ public class DisplayManagerGlobalTest {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
- null /* packageName */);
+ null /* packageName */, /* isEventFilterExplicit */ true);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler,
- DISPLAY_CHANGE_EVENTS, null /* packageName */);
+ DISPLAY_CHANGE_EVENTS, null /* packageName */,
+ /* isEventFilterExplicit */ true);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
waitForHandler();
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index c5c2554c2a67..fb743d2b42ad 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -77,6 +77,8 @@ public class PerfettoTraceTest {
private static final String TAG = "PerfettoTraceTest";
private static final String FOO = "foo";
private static final String BAR = "bar";
+ private static final String TEXT_ABOVE_4K_SIZE =
+ new String(new char[8192]).replace('\0', 'a');
private static final Category FOO_CATEGORY = new Category(FOO);
private static final int MESSAGE = 1234567;
@@ -155,41 +157,6 @@ public class PerfettoTraceTest {
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
- public void testDebugAnnotationsWithLambda() throws Exception {
- TraceConfig traceConfig = getTraceConfig(FOO);
-
- PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
-
- PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit();
-
- byte[] traceBytes = session.close();
-
- Trace trace = Trace.parseFrom(traceBytes);
-
- boolean hasTrackEvent = false;
- boolean hasDebugAnnotations = false;
- for (TracePacket packet: trace.getPacketList()) {
- TrackEvent event;
- if (packet.hasTrackEvent()) {
- hasTrackEvent = true;
- event = packet.getTrackEvent();
-
- if (TrackEvent.Type.TYPE_INSTANT.equals(event.getType())
- && event.getDebugAnnotationsCount() == 1) {
- hasDebugAnnotations = true;
-
- List<DebugAnnotation> annotations = event.getDebugAnnotationsList();
- assertThat(annotations.get(0).getIntValue()).isEqualTo(123L);
- }
- }
- }
-
- assertThat(hasTrackEvent).isTrue();
- assertThat(hasDebugAnnotations).isTrue();
- }
-
- @Test
- @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
public void testNamedTrack() throws Exception {
TraceConfig traceConfig = getTraceConfig(FOO);
@@ -440,7 +407,6 @@ public class PerfettoTraceTest {
boolean hasTrackEvent = false;
boolean hasSourceLocation = false;
-
for (TracePacket packet: trace.getPacketList()) {
TrackEvent event;
if (packet.hasTrackEvent()) {
@@ -467,6 +433,53 @@ public class PerfettoTraceTest {
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testProtoWithSlowPath() throws Exception {
+ TraceConfig traceConfig = getTraceConfig(FOO);
+
+ PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
+
+ PerfettoTrace.instant(FOO_CATEGORY, "event_proto")
+ .beginProto()
+ .beginNested(33L)
+ .addField(4L, 2L)
+ .addField(3, TEXT_ABOVE_4K_SIZE)
+ .endNested()
+ .addField(2001, "AIDL::IActivityManager")
+ .endProto()
+ .emit();
+
+ byte[] traceBytes = session.close();
+
+ Trace trace = Trace.parseFrom(traceBytes);
+
+ boolean hasTrackEvent = false;
+ boolean hasSourceLocation = false;
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+
+ if (TrackEvent.Type.TYPE_INSTANT.equals(event.getType())
+ && event.hasSourceLocation()) {
+ SourceLocation loc = event.getSourceLocation();
+ if (TEXT_ABOVE_4K_SIZE.equals(loc.getFunctionName())
+ && loc.getLineNumber() == 2) {
+ hasSourceLocation = true;
+ }
+ }
+ }
+
+ collectInternedData(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(hasSourceLocation).isTrue();
+ assertThat(mCategoryNames).contains(FOO);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
public void testProtoNested() throws Exception {
TraceConfig traceConfig = getTraceConfig(FOO);
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index e6361e10cfa7..6adceb96d977 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -230,7 +230,9 @@ public class InsetsSourceConsumerTest {
new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
false /* initialVisible */, new Point(), Insets.NONE),
new int[1], hideTypes, new int[1], new int[1]);
- assertTrue(mRemoveSurfaceCalled);
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ assertTrue(mRemoveSurfaceCalled);
+ }
assertEquals(0, hideTypes[0]);
});
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index a289df0441e5..c40137f1bd34 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,8 +16,6 @@
package android.view;
-import static android.app.UiModeManager.MODE_NIGHT_NO;
-import static android.app.UiModeManager.MODE_NIGHT_YES;
import static android.util.SequenceUtils.getInitSeq;
import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
@@ -69,10 +67,8 @@ import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.app.Instrumentation;
import android.app.UiModeManager;
-import android.app.UiModeManager.ForceInvertType;
import android.content.Context;
import android.graphics.ForceDarkType;
-import android.graphics.ForceDarkType.ForceDarkTypeDef;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
@@ -97,12 +93,9 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.TestUtils;
import com.android.cts.input.BlockingQueueEventVerifier;
import com.android.window.flags.Flags;
-import com.google.common.truth.Expect;
-
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.AfterClass;
@@ -131,8 +124,6 @@ public class ViewRootImplTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- @Rule
- public final Expect mExpect = Expect.create();
private ViewRootImpl mViewRootImpl;
private View mView;
@@ -1516,34 +1507,49 @@ public class ViewRootImplTest {
}
@Test
- @RequiresFlagsEnabled(FLAG_FORCE_INVERT_COLOR)
- public void updateConfiguration_returnsExpectedForceDarkMode() {
- waitForSystemNightModeActivated(true);
-
- verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true,
- UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK);
- verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false,
- UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
- verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true,
- UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK);
- verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false,
- UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
-
- waitForSystemNightModeActivated(false);
-
- verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true,
- UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
- verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false,
- UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
- verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true,
- UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
- verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false,
- UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
+ mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* value= */ 0
+ );
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
+ );
+
+ assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
+ }
+
+ @Test
+ public void forceInvertOnDarkThemeOff_forceDarkModeEnabled() {
+ mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* value= */ 1
+ );
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
+ );
+
+ assertThat(mViewRootImpl.determineForceDarkType())
+ .isEqualTo(ForceDarkType.FORCE_INVERT_COLOR_DARK);
}
@Test
- @EnableFlags(FLAG_FORCE_INVERT_COLOR)
public void forceInvertOffForceDarkOff_forceDarkModeDisabled() {
+ mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
Settings.Secure.putInt(
sContext.getContentResolver(),
@@ -1556,14 +1562,15 @@ public class ViewRootImplTest {
});
sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
+ );
assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
}
@Test
- @EnableFlags(FLAG_FORCE_INVERT_COLOR)
public void forceInvertOffForceDarkOn_forceDarkModeEnabled() {
+ mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
Settings.Secure.putInt(
sContext.getContentResolver(),
@@ -1575,7 +1582,8 @@ public class ViewRootImplTest {
});
sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
+ );
assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.FORCE_DARK);
}
@@ -1782,39 +1790,4 @@ public class ViewRootImplTest {
() -> view.getViewTreeObserver().removeOnDrawListener(listener));
}
}
-
- private void waitForSystemNightModeActivated(boolean active) {
- ShellIdentityUtils.invokeWithShellPermissions(() ->
- sInstrumentation.runOnMainSync(() -> {
- var uiModeManager = sContext.getSystemService(UiModeManager.class);
- uiModeManager.setNightModeActivated(active);
- }));
- sInstrumentation.waitForIdleSync();
- }
-
- private void verifyForceDarkType(boolean isAppInNightMode, boolean isForceInvertEnabled,
- @ForceInvertType int expectedForceInvertType,
- @ForceDarkTypeDef int expectedForceDarkType) {
- var uiModeManager = sContext.getSystemService(UiModeManager.class);
- ShellIdentityUtils.invokeWithShellPermissions(() -> {
- uiModeManager.setApplicationNightMode(
- isAppInNightMode ? MODE_NIGHT_YES : MODE_NIGHT_NO);
- Settings.Secure.putInt(
- sContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- isForceInvertEnabled ? 1 : 0);
- });
-
- sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
- try {
- TestUtils.waitUntil("Waiting for force invert state changed",
- () -> (uiModeManager.getForceInvertState() == expectedForceInvertType));
- } catch (Exception e) {
- Log.e(TAG, "Unexpected error trying to apply force invert state. " + e);
- e.printStackTrace();
- }
-
- mExpect.that(mViewRootImpl.determineForceDarkType()).isEqualTo(expectedForceDarkType);
- }
}
diff --git a/core/tests/coretests/src/android/view/ViewRootRectTrackerTest.java b/core/tests/coretests/src/android/view/ViewRootRectTrackerTest.java
new file mode 100644
index 000000000000..c66e10079bc8
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewRootRectTrackerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.view;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.testng.AssertJUnit.assertEquals;
+
+import android.graphics.Rect;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewRootRectTrackerTest {
+ private ViewRootRectTracker mTracker;
+ private final List<Rect> mRects = Lists.newArrayList(new Rect(0, 0, 5, 5),
+ new Rect(5, 5, 10, 10));
+
+ @Before
+ public void setUp() {
+ mTracker = new ViewRootRectTracker(v -> Collections.emptyList());
+ }
+
+ @Test
+ public void setRootRectsAndComputeTest() {
+ mTracker.setRootRects(mRects);
+ mTracker.computeChanges();
+ assertEquals(mRects, mTracker.getLastComputedRects());
+ }
+
+ @Test
+ public void waitingForComputeChangesTest() {
+ mTracker.setRootRects(mRects);
+ assertTrue(mTracker.isWaitingForComputeChanges());
+ mTracker.computeChangedRects();
+ assertFalse(mTracker.isWaitingForComputeChanges());
+
+ View mockView = mock(View.class);
+ mTracker.updateRectsForView(mockView);
+ assertTrue(mTracker.isWaitingForComputeChanges());
+ mTracker.computeChangedRects();
+ assertFalse(mTracker.isWaitingForComputeChanges());
+ }
+}
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index a63cbee4d707..fdbee3ccbebe 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -42,3 +42,11 @@ flag {
description: "Add DISPLAY_BT2020 ColorSpace support"
bug: "344038816"
}
+
+flag {
+ name: "gradient_drawable_shape_rounded_cap"
+ is_fixed_read_only: true
+ namespace: "core_graphics"
+ description: "Make GradientDrawable support drawing ring with rounded stroke cap."
+ bug: "380000245"
+}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 29d033e64aea..ff1dc93d787b 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -16,7 +16,11 @@
package android.graphics.drawable;
+import static com.android.graphics.flags.Flags.FLAG_GRADIENT_DRAWABLE_SHAPE_ROUNDED_CAP;
+import static com.android.graphics.flags.Flags.gradientDrawableShapeRoundedCap;
+
import android.annotation.ColorInt;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -125,8 +129,14 @@ public class GradientDrawable extends Drawable {
*/
public static final int RING = 3;
+ /**
+ * Shape is an arc.
+ */
+ @FlaggedApi(FLAG_GRADIENT_DRAWABLE_SHAPE_ROUNDED_CAP)
+ public static final int ARC = 4;
+
/** @hide */
- @IntDef({RECTANGLE, OVAL, LINE, RING})
+ @IntDef({RECTANGLE, OVAL, LINE, RING, ARC})
@Retention(RetentionPolicy.SOURCE)
public @interface Shape {}
@@ -167,6 +177,17 @@ public class GradientDrawable extends Drawable {
@Retention(RetentionPolicy.SOURCE)
public @interface RadiusType {}
+ private static final int BUTT = 0;
+
+ private static final int ROUND = 1;
+
+ private static final int SQUARE = 2;
+
+ /** @hide */
+ @IntDef({BUTT, ROUND, SQUARE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StrokeCap {}
+
private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f;
private static final float DEFAULT_THICKNESS_RATIO = 9.0f;
@@ -191,6 +212,8 @@ public class GradientDrawable extends Drawable {
private boolean mMutated;
private Path mRingPath;
private boolean mPathIsDirty = true;
+ private Path mArcPath;
+ private Path mArcOutlinePath;
/** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */
private float mGradientRadius;
@@ -850,6 +873,55 @@ public class GradientDrawable extends Drawable {
}
break;
}
+ case ARC:
+ if (gradientDrawableShapeRoundedCap()) {
+ // TODO(b/394988176): Consider applying ARC drawing logic to RING shape.
+ float centerX = mRect.centerX();
+ float centerY = mRect.centerY();
+ float thickness =
+ st.mThickness != -1 ? st.mThickness
+ : mRect.width() / st.mThicknessRatio;
+ float radius = st.mInnerRadius != -1 ? st.mInnerRadius
+ : mRect.width() / st.mInnerRadiusRatio;
+ radius -= thickness;
+ float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f;
+ mRect.set(centerX - radius, centerY - radius, centerX + radius,
+ centerY + radius);
+
+ // Prepare paint. Set style to STROKE for purpose of drawing line ARC.
+ mFillPaint.setStyle(Paint.Style.STROKE);
+ mFillPaint.setStrokeWidth(thickness);
+ mFillPaint.setStrokeCap(getStrokeLineCapForPaint(st.mStrokeCap));
+ canvas.drawArc(mRect, 0.0f, sweep, /* useCenter= */ false, mFillPaint);
+
+ if (haveStroke) {
+ if (mArcPath == null) {
+ mArcPath = new Path();
+ } else {
+ mArcPath.reset();
+ }
+ if (mArcOutlinePath == null) {
+ mArcOutlinePath = new Path();
+ } else {
+ mArcOutlinePath.reset();
+ }
+ if (sweep == 360f) {
+ mArcPath.addOval(mRect, Path.Direction.CW);
+ } else {
+ mArcPath.arcTo(mRect, 0.0f, sweep, /* forceMoveTo= */ false);
+ }
+
+ // The arc path doesn't have width. So, to get the outline of the result arc
+ // shape, we need to apply the paint effect to the path; then use the
+ // output as the result outline.
+ mFillPaint.getFillPath(mArcPath, mArcOutlinePath);
+ canvas.drawPath(mArcOutlinePath, mStrokePaint);
+ }
+
+ // Restore to FILL
+ mFillPaint.setStyle(Paint.Style.FILL);
+ break;
+ }
case RING:
Path path = buildRing(st);
canvas.drawPath(path, mFillPaint);
@@ -993,6 +1065,36 @@ public class GradientDrawable extends Drawable {
}
/**
+ * Return current drawable's stroke line cap. Note that this is only respected when drawable is
+ * {@link Shape#ARC}.
+ *
+ * @return the {@link StrokeCap} of current drawable.
+ * @attr ref android.R.styleable#GradientDrawable_strokeCap
+ * @see #setStrokeCap(int)
+ *
+ * @hide
+ */
+ @StrokeCap
+ public int getStrokeCap() {
+ return mGradientState.mStrokeCap;
+ }
+
+ /**
+ * Configure the stroke line cap type that drawable will use while drawing. Note that this is
+ * only respected when drawable is {@link Shape#ARC}.
+ *
+ * @param strokeCapType the stroke line cap type that the drawable will use while drawing.
+ * @attr ref android.R.styleable#GradientDrawable_strokeCap
+ * @see #getStrokeCap
+ *
+ * @hide
+ */
+ public void setStrokeCap(@StrokeCap int strokeCapType) {
+ mGradientState.mStrokeCap = strokeCapType;
+ invalidateSelf();
+ }
+
+ /**
* Configure the padding of the gradient shape
* @param left Left padding of the gradient shape
* @param top Top padding of the gradient shape
@@ -1066,6 +1168,15 @@ public class GradientDrawable extends Drawable {
return ringPath;
}
+ private Paint.Cap getStrokeLineCapForPaint(@StrokeCap int strokeLineCap) {
+ return switch (strokeLineCap) {
+ case BUTT -> Paint.Cap.BUTT;
+ case ROUND -> Paint.Cap.ROUND;
+ case SQUARE -> Paint.Cap.SQUARE;
+ default -> Paint.Cap.SQUARE;
+ };
+ }
+
/**
* Changes this drawable to use a single color instead of a gradient.
* <p>
@@ -1484,7 +1595,7 @@ public class GradientDrawable extends Drawable {
state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape);
state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither);
- if (state.mShape == RING) {
+ if (state.mShape == RING || state.mShape == ARC) {
state.mInnerRadius = a.getDimensionPixelSize(
R.styleable.GradientDrawable_innerRadius, state.mInnerRadius);
@@ -1503,6 +1614,9 @@ public class GradientDrawable extends Drawable {
state.mUseLevelForShape = a.getBoolean(
R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape);
+
+ state.mStrokeCap = a.getInt(
+ R.styleable.GradientDrawable_strokeCap, state.mStrokeCap);
}
final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1);
@@ -2045,6 +2159,9 @@ public class GradientDrawable extends Drawable {
public int mInnerRadius = -1;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218)
public int mThickness = -1;
+ @UnsupportedAppUsage(trackingBug = 380000245)
+ @StrokeCap public int mStrokeCap = ROUND;
+
public boolean mDither = false;
public Insets mOpticalInsets = Insets.NONE;
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f5f3f0fe52eb..a0c68ad44379 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -670,7 +670,4 @@
<dimen name="desktop_windowing_education_promo_height">352dp</dimen>
<!-- The corner radius of the desktop windowing education promo. -->
<dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen>
-
- <!-- The corner radius of freeform tasks in desktop windowing. -->
- <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 11a6f32d7454..23c9caf2046c 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -46,4 +46,7 @@
<dimen name="drop_target_expanded_view_height">578</dimen>
<dimen name="drop_target_expanded_view_padding_bottom">108</dimen>
<dimen name="drop_target_expanded_view_padding_horizontal">24</dimen>
+
+ <!-- The corner radius of freeform tasks in desktop windowing. -->
+ <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
</resources> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index 14338a49ee2f..0e4a6b9fb083 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -16,12 +16,14 @@
package com.android.wm.shell.shared.desktopmode
+import android.Manifest.permission.SYSTEM_ALERT_WINDOW
import android.app.TaskInfo
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED
import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION
import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
+import android.content.pm.PackageManager
import android.window.DesktopModeFlags
import com.android.internal.R
@@ -32,8 +34,10 @@ import com.android.internal.R
class DesktopModeCompatPolicy(private val context: Context) {
private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi)
+ private val pkgManager: PackageManager
+ get() = context.getPackageManager()
private val defaultHomePackage: String?
- get() = context.getPackageManager().getHomeActivities(ArrayList())?.packageName
+ get() = pkgManager.getHomeActivities(ArrayList())?.packageName
/**
* If the top activity should be exempt from desktop windowing and forced back to fullscreen.
@@ -47,11 +51,12 @@ class DesktopModeCompatPolicy(private val context: Context) {
fun isTopActivityExemptFromDesktopWindowing(packageName: String?,
numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
- DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
- && ((isSystemUiTask(packageName)
- || isPartOfDefaultHomePackageOrNoHomeAvailable(packageName)
- || isTransparentTask(isActivityStackTransparent, numActivities))
- && !isTopActivityNoDisplay)
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue &&
+ ((isSystemUiTask(packageName) ||
+ isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) ||
+ (isTransparentTask(isActivityStackTransparent, numActivities) &&
+ hasFullscreenTransparentPermission(packageName))) &&
+ !isTopActivityNoDisplay)
/**
* Whether the caption insets should be excluded from configuration for system to handle.
@@ -83,6 +88,26 @@ class DesktopModeCompatPolicy(private val context: Context) {
private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
+ // Checks if the app for the given package has the SYSTEM_ALERT_WINDOW permission.
+ private fun hasFullscreenTransparentPermission(packageName: String?): Boolean {
+ if (DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue) {
+ if (packageName == null) {
+ return false
+ }
+ return try {
+ val packageInfo = pkgManager.getPackageInfo(
+ packageName,
+ PackageManager.GET_PERMISSIONS
+ )
+ packageInfo?.requestedPermissions?.contains(SYSTEM_ALERT_WINDOW) == true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false // Package not found
+ }
+ }
+ // If the flag is disabled we make this condition neutral.
+ return true
+ }
+
/**
* Returns true if the tasks base activity is part of the default home package, or there is
* currently no default home package available.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index 5bd8d86f1144..0f1bf5e09751 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -15,6 +15,8 @@
*/
package com.android.wm.shell.bubbles;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+
import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.Context;
@@ -35,7 +37,6 @@ import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.icons.DotRenderer;
-import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -132,7 +133,7 @@ public class BadgedImageView extends ConstraintLayout {
private void getOutline(Outline outline) {
final int bubbleSize = mPositioner.getBubbleSize();
- final int normalizedSize = IconNormalizer.getNormalizedCircleSize(bubbleSize);
+ final int normalizedSize = Math.round(ICON_VISIBLE_AREA_FACTOR * bubbleSize);
final int inset = (bubbleSize - normalizedSize) / 2;
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index f6a2c8d9695e..305fcdd5fb7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -912,7 +912,7 @@ public class BubbleController implements ConfigurationChangeListener,
// TODO(b/393172431) : Utilise DragZoneFactory once it is ready
final int bubbleBarDropZoneSideSize = getContext().getResources().getDimensionPixelSize(
R.dimen.bubble_bar_drop_zone_side_size);
- int top = t - bubbleBarDropZoneSideSize;
+ int top = b - bubbleBarDropZoneSideSize;
result.put(BubbleBarLocation.LEFT,
new Rect(l, top, l + bubbleBarDropZoneSideSize, b));
result.put(BubbleBarLocation.RIGHT,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 221c9332711e..33f1b94bac73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.content.Context;
@@ -31,7 +32,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -557,8 +557,7 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
public float getPointerPosition(float bubblePosition) {
// TODO: I don't understand why it works but it does - why normalized in portrait
// & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation?
- final float normalizedSize = IconNormalizer.getNormalizedCircleSize(
- getBubbleSize());
+ final float normalizedSize = Math.round(ICON_VISIBLE_AREA_FACTOR * getBubbleSize());
return showBubblesVertically()
? bubblePosition + (getBubbleSize() / 2f)
: bubblePosition + (normalizedSize / 2f) - mPointerWidth;
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 e3b0872df593..29837dc04423 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
@@ -135,6 +135,7 @@ public class BubbleBarLayerView extends FrameLayout
/** Shows the expanded view drop target at the requested {@link BubbleBarLocation location} */
public void showBubbleBarExtendedViewDropTarget(@NonNull BubbleBarLocation bubbleBarLocation) {
+ setVisibility(VISIBLE);
mBubbleExpandedViewPinController.showDropTarget(bubbleBarLocation);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 8377a35a9e7d..87a4115ccd3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -324,8 +324,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
applyVisibilityToLeash(imeSourceControl);
}
- if (!mImeShowing) {
- removeImeSurface(mDisplayId);
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (!mImeShowing) {
+ removeImeSurface(mDisplayId);
+ }
}
}
} else {
@@ -663,7 +665,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.hide(animatingLeash);
- removeImeSurface(mDisplayId);
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ removeImeSurface(mDisplayId);
+ }
if (android.view.inputmethod.Flags.refactorInsetsController()) {
setVisibleDirectly(false /* visible */, statsToken);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt
new file mode 100644
index 000000000000..7a5bc1383ccf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.RectF
+import android.view.SurfaceControl
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.shared.annotations.ShellDesktopThread
+
+/**
+ * Controller to manage the indicators that show users the current position of the dragged window on
+ * the new display when performing drag move across displays.
+ */
+class MultiDisplayDragMoveIndicatorController(
+ private val displayController: DisplayController,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val indicatorSurfaceFactory: MultiDisplayDragMoveIndicatorSurface.Factory,
+ @ShellDesktopThread private val desktopExecutor: ShellExecutor,
+) {
+ @ShellDesktopThread
+ private val dragIndicators =
+ mutableMapOf<Int, MutableMap<Int, MultiDisplayDragMoveIndicatorSurface>>()
+
+ /**
+ * Called during drag move, which started at [startDisplayId]. Updates the position and
+ * visibility of the drag move indicators for the [taskInfo] based on [boundsDp] on the
+ * destination displays ([displayIds]) as the dragged window moves. [transactionSupplier]
+ * provides a [SurfaceControl.Transaction] for applying changes to the indicator surfaces.
+ *
+ * It is executed on the [desktopExecutor] to prevent blocking the main thread and avoid jank,
+ * as creating and manipulating surfaces can be expensive.
+ */
+ fun onDragMove(
+ boundsDp: RectF,
+ startDisplayId: Int,
+ taskInfo: RunningTaskInfo,
+ displayIds: Set<Int>,
+ transactionSupplier: () -> SurfaceControl.Transaction,
+ ) {
+ desktopExecutor.execute {
+ for (displayId in displayIds) {
+ if (displayId == startDisplayId) {
+ // No need to render indicators on the original display where the drag started.
+ continue
+ }
+ val displayLayout = displayController.getDisplayLayout(displayId) ?: continue
+ val shouldBeVisible =
+ RectF.intersects(RectF(boundsDp), displayLayout.globalBoundsDp())
+ if (
+ dragIndicators[taskInfo.taskId]?.containsKey(displayId) != true &&
+ !shouldBeVisible
+ ) {
+ // Skip this display if:
+ // - It doesn't have an existing indicator that needs to be updated, AND
+ // - The latest dragged window bounds don't intersect with this display.
+ continue
+ }
+
+ val boundsPx =
+ MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
+ boundsDp,
+ displayLayout,
+ )
+
+ // Get or create the inner map for the current task.
+ val dragIndicatorsForTask =
+ dragIndicators.getOrPut(taskInfo.taskId) { mutableMapOf() }
+ dragIndicatorsForTask[displayId]?.also { existingIndicator ->
+ val transaction = transactionSupplier()
+ existingIndicator.relayout(boundsPx, transaction, shouldBeVisible)
+ transaction.apply()
+ } ?: run {
+ val newIndicator =
+ indicatorSurfaceFactory.create(
+ taskInfo,
+ displayController.getDisplay(displayId),
+ )
+ newIndicator.show(
+ transactionSupplier(),
+ taskInfo,
+ rootTaskDisplayAreaOrganizer,
+ displayId,
+ boundsPx,
+ )
+ dragIndicatorsForTask[displayId] = newIndicator
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when the drag ends. Disposes of the drag move indicator surfaces associated with the
+ * given [taskId]. [transactionSupplier] provides a [SurfaceControl.Transaction] for applying
+ * changes to the indicator surfaces.
+ *
+ * It is executed on the [desktopExecutor] to ensure that any pending `onDragMove` operations
+ * have completed before disposing of the surfaces.
+ */
+ fun onDragEnd(taskId: Int, transactionSupplier: () -> SurfaceControl.Transaction) {
+ desktopExecutor.execute {
+ dragIndicators.remove(taskId)?.values?.takeIf { it.isNotEmpty() }?.let { indicators ->
+ val transaction = transactionSupplier()
+ indicators.forEach { indicator ->
+ indicator.disposeSurface(transaction)
+ }
+ transaction.apply()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt
new file mode 100644
index 000000000000..d05d3b0903d7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.Trace
+import android.view.Display
+import android.view.SurfaceControl
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.ui.graphics.toArgb
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.common.Theme
+
+/**
+ * Represents the indicator surface that visualizes the current position of a dragged window during
+ * a multi-display drag operation.
+ *
+ * This class manages the creation, display, and manipulation of the [SurfaceControl]s that act as a
+ * visual indicator, providing feedback to the user about the dragged window's location.
+ */
+class MultiDisplayDragMoveIndicatorSurface(
+ context: Context,
+ taskInfo: RunningTaskInfo,
+ display: Display,
+ surfaceControlBuilderFactory: Factory.SurfaceControlBuilderFactory,
+) {
+ private var isVisible = false
+
+ // A container surface to host the veil background
+ private var veilSurface: SurfaceControl? = null
+
+ private val decorThemeUtil = DecorThemeUtil(context)
+ private val lightColors = dynamicLightColorScheme(context)
+ private val darkColors = dynamicDarkColorScheme(context)
+
+ init {
+ Trace.beginSection("DragIndicatorSurface#init")
+
+ val displayId = display.displayId
+ veilSurface =
+ surfaceControlBuilderFactory
+ .create("Drag indicator veil of Task=${taskInfo.taskId} Display=$displayId")
+ .setColorLayer()
+ .setCallsite("DragIndicatorSurface#init")
+ .setHidden(true)
+ .build()
+
+ // TODO: b/383069173 - Add icon for the surface.
+
+ Trace.endSection()
+ }
+
+ /**
+ * Disposes the indicator surface using the provided [transaction].
+ */
+ fun disposeSurface(transaction: SurfaceControl.Transaction) {
+ veilSurface?.let { veil -> transaction.remove(veil) }
+ veilSurface = null
+ }
+
+ /**
+ * Shows the indicator surface at [bounds] on the specified display ([displayId]),
+ * visualizing the drag of the [taskInfo]. The indicator surface is shown using [transaction],
+ * and the [rootTaskDisplayAreaOrganizer] is used to reparent the surfaces.
+ */
+ fun show(
+ transaction: SurfaceControl.Transaction,
+ taskInfo: RunningTaskInfo,
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ displayId: Int,
+ bounds: Rect,
+ ) {
+ val backgroundColor =
+ when (decorThemeUtil.getAppTheme(taskInfo)) {
+ Theme.LIGHT -> lightColors.surfaceContainer
+ Theme.DARK -> darkColors.surfaceContainer
+ }
+ val veil = veilSurface ?: return
+ isVisible = true
+
+ rootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, veil, transaction)
+ relayout(bounds, transaction, shouldBeVisible = true)
+ transaction.show(veil).setColor(veil, Color.valueOf(backgroundColor.toArgb()).components)
+ transaction.apply()
+ }
+
+ /**
+ * Repositions and resizes the indicator surface based on [bounds] using [transaction]. The
+ * [shouldBeVisible] flag indicates whether the indicator is within the display after relayout.
+ */
+ fun relayout(bounds: Rect, transaction: SurfaceControl.Transaction, shouldBeVisible: Boolean) {
+ if (!isVisible && !shouldBeVisible) {
+ // No need to relayout if the surface is already invisible and should not be visible.
+ return
+ }
+ isVisible = shouldBeVisible
+ val veil = veilSurface ?: return
+ transaction.setCrop(veil, bounds)
+ }
+
+ /**
+ * Factory for creating [MultiDisplayDragMoveIndicatorSurface] instances with the [context].
+ */
+ class Factory(private val context: Context) {
+ private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory =
+ object : SurfaceControlBuilderFactory {}
+
+ /**
+ * Creates a new [MultiDisplayDragMoveIndicatorSurface] instance to visualize the drag
+ * operation of the [taskInfo] on the given [display].
+ */
+ fun create(
+ taskInfo: RunningTaskInfo,
+ display: Display,
+ ) = MultiDisplayDragMoveIndicatorSurface(
+ context,
+ taskInfo,
+ display,
+ surfaceControlBuilderFactory,
+ )
+
+ /**
+ * Interface for creating [SurfaceControl.Builder] instances.
+ *
+ * This provides an abstraction over [SurfaceControl.Builder] creation for testing purposes.
+ */
+ interface SurfaceControlBuilderFactory {
+ fun create(name: String): SurfaceControl.Builder {
+ return SurfaceControl.Builder().setName(name)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2fd8c27d5970..5a246e7c99b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -68,6 +68,8 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorSurface;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -82,6 +84,7 @@ import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
+import com.android.wm.shell.desktopmode.DesktopDisplayModeController;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopMinimizationTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
@@ -989,7 +992,8 @@ public abstract class WMShellModule {
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DesktopTilingDecorViewModel desktopTilingDecorViewModel
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -1006,7 +1010,30 @@ public abstract class WMShellModule {
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy,
- desktopTilingDecorViewModel));
+ desktopTilingDecorViewModel,
+ multiDisplayDragMoveIndicatorController));
+ }
+
+ @WMSingleton
+ @Provides
+ static MultiDisplayDragMoveIndicatorController
+ providesMultiDisplayDragMoveIndicatorController(
+ DisplayController displayController,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ MultiDisplayDragMoveIndicatorSurface.Factory
+ multiDisplayDragMoveIndicatorSurfaceFactory,
+ @ShellDesktopThread ShellExecutor desktopExecutor
+ ) {
+ return new MultiDisplayDragMoveIndicatorController(
+ displayController, rootTaskDisplayAreaOrganizer,
+ multiDisplayDragMoveIndicatorSurfaceFactory, desktopExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static MultiDisplayDragMoveIndicatorSurface.Factory
+ providesMultiDisplayDragMoveIndicatorSurfaceFactory(Context context) {
+ return new MultiDisplayDragMoveIndicatorSurface.Factory(context);
}
@WMSingleton
@@ -1230,13 +1257,10 @@ public abstract class WMShellModule {
static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler(
Context context,
ShellInit shellInit,
- Transitions transitions,
DisplayController displayController,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- IWindowManager windowManager,
Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
- ShellTaskOrganizer shellTaskOrganizer
+ Optional<DesktopDisplayModeController> desktopDisplayModeController
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.empty();
@@ -1245,13 +1269,10 @@ public abstract class WMShellModule {
new DesktopDisplayEventHandler(
context,
shellInit,
- transitions,
displayController,
- rootTaskDisplayAreaOrganizer,
- windowManager,
desktopUserRepositories.get(),
desktopTasksController.get(),
- shellTaskOrganizer));
+ desktopDisplayModeController.get()));
}
@WMSingleton
@@ -1381,6 +1402,29 @@ public abstract class WMShellModule {
return new DesktopModeUiEventLogger(uiEventLogger, packageManager);
}
+ @WMSingleton
+ @Provides
+ static Optional<DesktopDisplayModeController> provideDesktopDisplayModeController(
+ Context context,
+ Transitions transitions,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ IWindowManager windowManager,
+ ShellTaskOrganizer shellTaskOrganizer,
+ DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider
+ ) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ new DesktopDisplayModeController(
+ context,
+ transitions,
+ rootTaskDisplayAreaOrganizer,
+ windowManager,
+ shellTaskOrganizer,
+ desktopWallpaperActivityTokenProvider));
+ }
+
//
// App zoom out
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index c38558d7bde9..946e7952dd50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -16,41 +16,25 @@
package com.android.wm.shell.desktopmode
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
-import android.app.WindowConfiguration.windowingModeToString
import android.content.Context
-import android.provider.Settings
-import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
import android.view.Display.DEFAULT_DISPLAY
-import android.view.IWindowManager
-import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DesktopExperienceFlags
-import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.transition.Transitions
/** Handles display events in desktop mode */
class DesktopDisplayEventHandler(
private val context: Context,
shellInit: ShellInit,
- private val transitions: Transitions,
private val displayController: DisplayController,
- private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- private val windowManager: IWindowManager,
private val desktopUserRepositories: DesktopUserRepositories,
private val desktopTasksController: DesktopTasksController,
- private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val desktopDisplayModeController: DesktopDisplayModeController,
) : OnDisplaysChangedListener, OnDeskRemovedListener {
private val desktopRepository: DesktopRepository
@@ -70,7 +54,7 @@ class DesktopDisplayEventHandler(
override fun onDisplayAdded(displayId: Int) {
if (displayId != DEFAULT_DISPLAY) {
- refreshDisplayWindowingMode()
+ desktopDisplayModeController.refreshDisplayWindowingMode()
}
if (!supportsDesks(displayId)) {
@@ -88,7 +72,7 @@ class DesktopDisplayEventHandler(
override fun onDisplayRemoved(displayId: Int) {
if (displayId != DEFAULT_DISPLAY) {
- refreshDisplayWindowingMode()
+ desktopDisplayModeController.refreshDisplayWindowingMode()
}
// TODO: b/362720497 - move desks in closing display to the remaining desk.
@@ -102,65 +86,6 @@ class DesktopDisplayEventHandler(
}
}
- private fun refreshDisplayWindowingMode() {
- if (!Flags.enableDisplayWindowingModeSwitching()) return
- // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
- val isExtendedDisplayEnabled =
- 0 !=
- Settings.Global.getInt(
- context.contentResolver,
- DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
- 0,
- )
- if (!isExtendedDisplayEnabled) {
- // No action needed in mirror or projected mode.
- return
- }
-
- val hasNonDefaultDisplay =
- rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
- displayId != DEFAULT_DISPLAY
- }
- val targetDisplayWindowingMode =
- if (hasNonDefaultDisplay) {
- WINDOWING_MODE_FREEFORM
- } else {
- // Use the default display windowing mode when no non-default display.
- windowManager.getWindowingMode(DEFAULT_DISPLAY)
- }
- val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
- requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
- val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
- if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
- // Already in the target mode.
- return
- }
-
- logV(
- "As an external display is connected, changing default display's windowing mode from" +
- " ${windowingModeToString(currentDisplayWindowingMode)}" +
- " to ${windowingModeToString(targetDisplayWindowingMode)}"
- )
-
- val wct = WindowContainerTransaction()
- wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
- shellTaskOrganizer
- .getRunningTasks(DEFAULT_DISPLAY)
- .filter { it.activityType == ACTIVITY_TYPE_STANDARD }
- .forEach {
- // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
- when (it.windowingMode) {
- currentDisplayWindowingMode -> {
- wct.setWindowingMode(it.token, currentDisplayWindowingMode)
- }
- targetDisplayWindowingMode -> {
- wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
- }
- }
- }
- transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
- }
-
// TODO: b/362720497 - connected/projected display considerations.
private fun supportsDesks(displayId: Int): Boolean =
DesktopModeStatus.canEnterDesktopMode(context)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
new file mode 100644
index 000000000000..c9a63ff818f5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.windowingModeToString
+import android.content.Context
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.transition.Transitions
+
+/** Controls the display windowing mode in desktop mode */
+class DesktopDisplayModeController(
+ private val context: Context,
+ private val transitions: Transitions,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val windowManager: IWindowManager,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+) {
+
+ fun refreshDisplayWindowingMode() {
+ if (!Flags.enableDisplayWindowingModeSwitching()) return
+ // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
+ val isExtendedDisplayEnabled =
+ 0 !=
+ Settings.Global.getInt(
+ context.contentResolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+ 0,
+ )
+ if (!isExtendedDisplayEnabled) {
+ // No action needed in mirror or projected mode.
+ return
+ }
+
+ val hasNonDefaultDisplay =
+ rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
+ displayId != DEFAULT_DISPLAY
+ }
+ val targetDisplayWindowingMode =
+ if (hasNonDefaultDisplay) {
+ WINDOWING_MODE_FREEFORM
+ } else {
+ // Use the default display windowing mode when no non-default display.
+ windowManager.getWindowingMode(DEFAULT_DISPLAY)
+ }
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
+ requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
+ val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
+ // Already in the target mode.
+ return
+ }
+
+ logV(
+ "As an external display is connected, changing default display's windowing mode from" +
+ " ${windowingModeToString(currentDisplayWindowingMode)}" +
+ " to ${windowingModeToString(targetDisplayWindowingMode)}"
+ )
+
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
+ shellTaskOrganizer
+ .getRunningTasks(DEFAULT_DISPLAY)
+ .filter { it.activityType == ACTIVITY_TYPE_STANDARD }
+ .forEach {
+ // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
+ when (it.windowingMode) {
+ currentDisplayWindowingMode -> {
+ wct.setWindowingMode(it.token, currentDisplayWindowingMode)
+ }
+ targetDisplayWindowingMode -> {
+ wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
+ }
+ }
+ }
+ // The override windowing mode of DesktopWallpaper can be UNDEFINED on fullscreen-display
+ // right after the first launch while its resolved windowing mode is FULLSCREEN. We here
+ // it has the FULLSCREEN override windowing mode.
+ desktopWallpaperActivityTokenProvider.getToken(DEFAULT_DISPLAY)?.let { token ->
+ wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+ }
+ transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ }
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopDisplayModeController"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 113e3d067a90..7c6cf4a8b37f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -208,6 +208,7 @@ class DesktopRepository(
/** Adds the given desk under the given display. */
fun addDesk(displayId: Int, deskId: Int) {
+ logD("addDesk for displayId=%d and deskId=%d", displayId, deskId)
desktopData.createDesk(displayId, deskId)
}
@@ -224,6 +225,7 @@ class DesktopRepository(
/** Sets the given desk as the active one in the given display. */
fun setActiveDesk(displayId: Int, deskId: Int) {
+ logD("setActiveDesk for displayId=%d and deskId=%d", displayId, deskId)
desktopData.setActiveDesk(displayId = displayId, deskId = deskId)
}
@@ -246,6 +248,7 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun addTask(displayId: Int, taskId: Int, isVisible: Boolean) {
+ logD("addTask for displayId=%d, taskId=%d, isVisible=%b", displayId, taskId, isVisible)
val activeDesk =
checkNotNull(desktopData.getDefaultDesk(displayId)) {
"Expected desk in display: $displayId"
@@ -254,6 +257,13 @@ class DesktopRepository(
}
fun addTaskToDesk(displayId: Int, deskId: Int, taskId: Int, isVisible: Boolean) {
+ logD(
+ "addTaskToDesk for displayId=%d, deskId=%d, taskId=%d, isVisible=%b",
+ displayId,
+ deskId,
+ taskId,
+ isVisible,
+ )
addOrMoveTaskToTopOfDesk(displayId = displayId, deskId = deskId, taskId = taskId)
addActiveTaskToDesk(displayId = displayId, deskId = deskId, taskId = taskId)
updateTaskInDesk(
@@ -265,6 +275,12 @@ class DesktopRepository(
}
private fun addActiveTaskToDesk(displayId: Int, deskId: Int, taskId: Int) {
+ logD(
+ "addActiveTaskToDesk for displayId=%d, deskId=%d, taskId=%d",
+ displayId,
+ deskId,
+ taskId,
+ )
val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" }
// Removes task if it is active on another desk excluding this desk.
@@ -279,6 +295,7 @@ class DesktopRepository(
/** Removes task from active task list of desks excluding the [excludedDeskId]. */
@VisibleForTesting
fun removeActiveTask(taskId: Int, excludedDeskId: Int? = null) {
+ logD("removeActiveTask for taskId=%d, excludedDeskId=%d", taskId, excludedDeskId)
val affectedDisplays = mutableSetOf<Int>()
desktopData
.desksSequence()
@@ -303,6 +320,7 @@ class DesktopRepository(
taskId: Int,
notifyListeners: Boolean = true,
): Boolean {
+ logD("removeActiveTaskFromDesk for deskId=%d, taskId=%d", deskId, taskId)
val desk = desktopData.getDesk(deskId) ?: return false
if (desk.activeTasks.remove(taskId)) {
logD("Removed active task=%d from deskId=%d", taskId, desk.deskId)
@@ -463,7 +481,7 @@ class DesktopRepository(
/** Removes task from visible tasks of all desks except [excludedDeskId]. */
private fun removeVisibleTask(taskId: Int, excludedDeskId: Int? = null) {
- desktopData.forAllDesks { displayId, desk ->
+ desktopData.forAllDesks { _, desk ->
if (desk.deskId != excludedDeskId) {
removeVisibleTaskFromDesk(deskId = desk.deskId, taskId = taskId)
}
@@ -675,6 +693,11 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) {
+ logD(
+ "Top transparent fullscreen task set for display: taskId=%d, displayId=%d",
+ taskId,
+ displayId,
+ )
desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId
}
@@ -692,6 +715,11 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun clearTopTransparentFullscreenTaskId(displayId: Int) {
+ logD(
+ "Top transparent fullscreen task cleared for display: taskId=%d, displayId=%d",
+ desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId,
+ displayId,
+ )
desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null
}
@@ -725,6 +753,12 @@ class DesktopRepository(
* Unminimizes the task if it is minimized.
*/
private fun addOrMoveTaskToTopOfDesk(displayId: Int, deskId: Int, taskId: Int) {
+ logD(
+ "addOrMoveTaskToTopOfDesk displayId=%d, deskId=%d, taskId=%d",
+ displayId,
+ deskId,
+ taskId,
+ )
val desk = desktopData.getDesk(deskId) ?: error("Could not find desk: $deskId")
logD("addOrMoveTaskToTopOfDesk: display=%d deskId=%d taskId=%d", displayId, deskId, taskId)
desktopData.forAllDesks { _, desk1 -> desk1.freeformTasksInZOrder.remove(taskId) }
@@ -745,6 +779,7 @@ class DesktopRepository(
* desk id instead of using this function and defaulting to the active one.
*/
fun minimizeTask(displayId: Int, taskId: Int) {
+ logD("minimizeTask displayId=%d, taskId=%d", displayId, taskId)
if (displayId == INVALID_DISPLAY) {
// When a task vanishes it doesn't have a displayId. Find the display of the task and
// mark it as minimized.
@@ -763,7 +798,7 @@ class DesktopRepository(
/** Minimizes the task in its desk. */
@VisibleForTesting
fun minimizeTaskInDesk(displayId: Int, deskId: Int, taskId: Int) {
- logD("Minimize Task: displayId=%d deskId=%d, task=%d", displayId, deskId, taskId)
+ logD("MinimizeTaskInDesk: displayId=%d deskId=%d, task=%d", displayId, deskId, taskId)
desktopData.getDesk(deskId)?.minimizedTasks?.add(taskId)
?: logD("Minimize task: No active desk found for task: taskId=%d", taskId)
updateTaskInDesk(displayId, deskId, taskId, isVisible = false)
@@ -778,12 +813,12 @@ class DesktopRepository(
* TODO: b/389960283 - consider using [unminimizeTaskFromDesk] instead.
*/
fun unminimizeTask(displayId: Int, taskId: Int) {
- logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
+ logD("UnminimizeTask: display=%d, task=%d", displayId, taskId)
desktopData.forAllDesks(displayId) { desk -> unminimizeTaskFromDesk(desk.deskId, taskId) }
}
private fun unminimizeTaskFromDesk(deskId: Int, taskId: Int) {
- logD("Unminimize Task: deskId=%d, taskId=%d", deskId, taskId)
+ logD("Unminimize Task from desk: deskId=%d, taskId=%d", deskId, taskId)
if (desktopData.getDesk(deskId)?.minimizedTasks?.remove(taskId) != true) {
logW("Unminimize Task: deskId=%d, taskId=%d, no task data", deskId, taskId)
}
@@ -854,6 +889,7 @@ class DesktopRepository(
/** Removes the given desk and returns the active tasks in that desk. */
fun removeDesk(deskId: Int): Set<Int> {
+ logD("removeDesk %d", deskId)
val desk =
desktopData.getDesk(deskId)
?: return emptySet<Int>().also {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
index e831d5eecdc2..6034299453fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
@@ -19,13 +19,16 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.window.DesktopModeFlags
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.freeform.TaskChangeListener
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
/** Manages tasks handling specific to Android Desktop Mode. */
class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUserRepositories) :
TaskChangeListener {
override fun onTaskOpening(taskInfo: RunningTaskInfo) {
+ logD("onTaskOpening for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId)
val desktopRepository: DesktopRepository =
desktopUserRepositories.getProfile(taskInfo.userId)
if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) {
@@ -38,6 +41,7 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
}
override fun onTaskChanging(taskInfo: RunningTaskInfo) {
+ logD("onTaskChanging for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId)
val desktopRepository: DesktopRepository =
desktopUserRepositories.getProfile(taskInfo.userId)
if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
@@ -67,9 +71,15 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
// of race conditions and possible duplications with [onTaskChanging].
override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) {
// TODO: b/367268953 - Propagate usages from FreeformTaskListener to this method.
+ logD(
+ "onNonTransitionTaskChanging for taskId=%d, displayId=%d",
+ taskInfo.taskId,
+ taskInfo.displayId,
+ )
}
override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) {
+ logD("onTaskMovingToFront for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId)
val desktopRepository: DesktopRepository =
desktopUserRepositories.getProfile(taskInfo.userId)
if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
@@ -80,10 +90,12 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
}
override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) {
+ logD("onTaskMovingToBack for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId)
// TODO: b/367268953 - Connect this with DesktopRepository.
}
override fun onTaskClosing(taskInfo: RunningTaskInfo) {
+ logD("onTaskClosing for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId)
val desktopRepository: DesktopRepository =
desktopUserRepositories.getProfile(taskInfo.userId)
if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
@@ -104,4 +116,12 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean =
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopTaskChangeListener"
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index cdbf9d901e9c..45adfe4112a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -117,6 +117,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
+import com.android.wm.shell.shared.R as SharedR
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellDesktopThread
@@ -2040,7 +2041,9 @@ class DesktopTasksController(
}
val cornerRadius =
context.resources
- .getDimensionPixelSize(R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+ .getDimensionPixelSize(
+ SharedR.dimen.desktop_windowing_freeform_rounded_corner_radius
+ )
.toFloat()
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
index 59add47fc79d..5f45192569e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
@@ -18,13 +18,11 @@ package com.android.wm.shell.desktopmode;
/**
* Defines the state of desks on a display whose ID is `displayId`, which is:
- * - `canCreateDesks`: whether it's possible to create new desks on this display.
* - `activeDeskId`: the currently active desk Id, or `-1` if none is active.
* - `deskId`: the list of desk Ids of the available desks on this display.
*/
parcelable DisplayDeskState {
int displayId;
- boolean canCreateDesk;
int activeDeskId;
int[] deskIds;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index b46051c51fcc..cb231800bd63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -43,7 +43,7 @@ import com.android.wm.shell.bubbles.BubbleTransitions
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
-import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
@@ -120,10 +120,7 @@ sealed class DragToDesktopTransitionHandler(
dragToDesktopAnimator: MoveToDesktopAnimator,
) {
if (inProgress) {
- ProtoLog.v(
- ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "DragToDesktop: Drag to desktop transition already in progress.",
- )
+ logV("Drag to desktop transition already in progress.")
return
}
@@ -537,12 +534,14 @@ sealed class DragToDesktopTransitionHandler(
state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
) {
+ logV("mergeAnimation: cancel through split")
clearState()
return
}
// In case of bubble animation, finish the initial desktop drag animation, but keep the
// current animation running and have bubbles take over
if (info.type == TRANSIT_CONVERT_TO_BUBBLE) {
+ logV("mergeAnimation: convert-to-bubble")
state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
clearState()
return
@@ -562,6 +561,7 @@ sealed class DragToDesktopTransitionHandler(
state.startTransitionFinishCb
?: error("Start transition expected to be waiting for merge but wasn't")
if (isEndTransition) {
+ logV("mergeAnimation: end-transition, target=$mergeTarget")
setupEndDragToDesktop(
info,
startTransaction = startT,
@@ -572,7 +572,10 @@ sealed class DragToDesktopTransitionHandler(
LatencyTracker.getInstance(context)
.onActionEnd(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG)
animateEndDragToDesktop(startTransaction = startT, startTransitionFinishCb)
- } else if (isCancelTransition) {
+ return
+ }
+ if (isCancelTransition) {
+ logV("mergeAnimation: cancel-transition, target=$mergeTarget")
LatencyTracker.getInstance(context)
.onActionCancel(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG)
info.changes.forEach { change ->
@@ -583,7 +586,9 @@ sealed class DragToDesktopTransitionHandler(
finishCallback.onTransitionFinished(/* wct= */ null)
startTransitionFinishCb.onTransitionFinished(/* wct= */ null)
clearState()
+ return
}
+ logW("unhandled merge transition: transitionInfo=$info")
}
protected open fun setupEndDragToDesktop(
@@ -724,10 +729,7 @@ sealed class DragToDesktopTransitionHandler(
return
}
if (state.startTransitionToken == transition) {
- ProtoLog.v(
- ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "DragToDesktop: onTransitionConsumed() start transition aborted",
- )
+ logV("onTransitionConsumed() start transition aborted")
state.startAborted = true
// The start-transition (DRAG_HOLD) is aborted, cancel its jank interaction.
interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
@@ -950,7 +952,16 @@ sealed class DragToDesktopTransitionHandler(
CANCEL_BUBBLE_RIGHT,
}
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private fun logW(msg: String, vararg arguments: Any?) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
companion object {
+ private const val TAG = "DragToDesktopTransitionHandler"
/** The duration of the animation to commit or cancel the drag-to-desktop gesture. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
@@ -1052,10 +1063,7 @@ constructor(
val state = requireTransitionState()
val homeLeash = state.homeChange?.leash
if (homeLeash == null) {
- ProtoLog.e(
- ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "DragToDesktop: home leash is null",
- )
+ logE("home leash is null")
} else {
// Hide home on finish to prevent flickering when wallpaper activity flag is enabled
finishTransaction.hide(homeLeash)
@@ -1096,6 +1104,12 @@ constructor(
val startBoundsWithOffset =
Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) }
+ logV(
+ "animateEndDragToDesktop: startBounds=$startBounds, endBounds=$endBounds, " +
+ "startScale=$startScale, startPosition=$startPosition, " +
+ "startBoundsWithOffset=$startBoundsWithOffset"
+ )
+
dragToDesktopStateListener?.onCommitToDesktopAnimationStart()
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
@@ -1177,7 +1191,16 @@ constructor(
.start()
}
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private fun logE(msg: String, vararg arguments: Any?) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
companion object {
+ private const val TAG = "SpringDragToDesktopTransitionHandler"
/** The freeform tasks initial scale when committing the drag-to-desktop gesture. */
private val FREEFORM_TASKS_INITIAL_SCALE =
propertyValue("freeform_tasks_initial_scale", scale = 100f, default = 0.9f)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 7ed1581cdfdb..cefbd8947feb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -28,7 +28,7 @@ oneway interface IDesktopTaskListener {
* Called once when the listener first gets connected to initialize it with the current state of
* desks in Shell.
*/
- void onListenerConnected(in DisplayDeskState[] displayDeskStates);
+ void onListenerConnected(in DisplayDeskState[] displayDeskStates, boolean canCreateDesks);
/** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
@@ -49,10 +49,10 @@ oneway interface IDesktopTaskListener {
void onExitDesktopModeTransitionStarted(int transitionDuration);
/**
- * Called when the conditions that allow the creation of a new desk on the display whose ID is
- * `displayId` changes to `canCreateDesks`. It's also called when a new display is added.
+ * Called when the conditions that allow the creation of a new desk changes. This is a global
+ * state for the entire device.
*/
- void onCanCreateDesksChanged(int displayId, boolean canCreateDesks);
+ void onCanCreateDesksChanged(boolean canCreateDesks);
/** Called when a desk whose ID is `deskId` is added to the display whose ID is `displayId`. */
void onDeskAdded(int displayId, int deskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 847a0383e7d0..d6182e9a78dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -75,11 +75,11 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.Flags;
-import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.R;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.HomeTransitionObserver;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 5a6ea214e561..cf139a008164 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -103,6 +103,7 @@ import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -258,6 +259,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final RecentsTransitionHandler mRecentsTransitionHandler;
private final DesktopModeCompatPolicy mDesktopModeCompatPolicy;
private final DesktopTilingDecorViewModel mDesktopTilingDecorViewModel;
+ private final MultiDisplayDragMoveIndicatorController mMultiDisplayDragMoveIndicatorController;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -296,7 +298,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
this(
context,
shellExecutor,
@@ -340,7 +343,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
taskResourceLoader,
recentsTransitionHandler,
desktopModeCompatPolicy,
- desktopTilingDecorViewModel);
+ desktopTilingDecorViewModel,
+ multiDisplayDragMoveIndicatorController);
}
@VisibleForTesting
@@ -387,7 +391,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -460,6 +465,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDesktopModeCompatPolicy = desktopModeCompatPolicy;
mDesktopTilingDecorViewModel = desktopTilingDecorViewModel;
mDesktopTasksController.setSnapEventHandler(this);
+ mMultiDisplayDragMoveIndicatorController = multiDisplayDragMoveIndicatorController;
shellInit.addInitCallback(this::onInit, this);
}
@@ -1759,7 +1765,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mTransitions,
mInteractionJankMonitor,
mTransactionFactory,
- mMainHandler);
+ mMainHandler,
+ mMultiDisplayDragMoveIndicatorController);
windowDecoration.setTaskDragResizer(taskPositioner);
final DesktopModeTouchEventListener touchEventListener =
@@ -2056,7 +2063,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
Transitions transitions,
InteractionJankMonitor interactionJankMonitor,
Supplier<SurfaceControl.Transaction> transactionFactory,
- Handler handler) {
+ Handler handler,
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
// TODO(b/383632995): Update when the flag is launched.
? (Flags.enableConnectedDisplaysWindowDrag()
@@ -2067,7 +2075,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
dragEventListener,
transitions,
interactionJankMonitor,
- handler)
+ handler,
+ multiDisplayDragMoveIndicatorController)
: new VeiledResizeTaskPositioner(
taskOrganizer,
windowDecoration,
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 dca376f7df0e..6165dbf686fd 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
@@ -1069,7 +1069,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private static int getCornerRadius(@NonNull Context context, int layoutResId) {
if (layoutResId == R.layout.desktop_mode_app_header) {
return loadDimensionPixelSize(context.getResources(),
- R.dimen.desktop_windowing_freeform_rounded_corner_radius);
+ com.android.wm.shell.shared.R.dimen
+ .desktop_windowing_freeform_rounded_corner_radius);
}
return INVALID_CORNER_RADIUS;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index bb20292a51d4..c6cb62d153ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor
import android.graphics.PointF
import android.graphics.Rect
+import android.hardware.display.DisplayTopology
import android.os.Handler
import android.os.IBinder
import android.os.Looper
@@ -32,10 +33,10 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import java.util.concurrent.TimeUnit
-import java.util.function.Supplier
/**
* A task positioner that also takes into account resizing a
@@ -49,11 +50,12 @@ class MultiDisplayVeiledResizeTaskPositioner(
private val desktopWindowDecoration: DesktopModeWindowDecoration,
private val displayController: DisplayController,
dragEventListener: DragPositioningCallbackUtility.DragEventListener,
- private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val transactionSupplier: () -> SurfaceControl.Transaction,
private val transitions: Transitions,
private val interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread private val handler: Handler,
-) : TaskPositioner, Transitions.TransitionHandler {
+ private val multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController,
+) : TaskPositioner, Transitions.TransitionHandler, DisplayController.OnDisplaysChangedListener {
private val dragEventListeners =
mutableListOf<DragPositioningCallbackUtility.DragEventListener>()
private val stableBounds = Rect()
@@ -71,6 +73,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
private var isResizingOrAnimatingResize = false
@Surface.Rotation private var rotation = 0
private var startDisplayId = 0
+ private val displayIds = mutableSetOf<Int>()
constructor(
taskOrganizer: ShellTaskOrganizer,
@@ -80,19 +83,22 @@ class MultiDisplayVeiledResizeTaskPositioner(
transitions: Transitions,
interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread handler: Handler,
+ multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController,
) : this(
taskOrganizer,
windowDecoration,
displayController,
dragEventListener,
- Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() },
+ { SurfaceControl.Transaction() },
transitions,
interactionJankMonitor,
handler,
+ multiDisplayDragMoveIndicatorController,
)
init {
dragEventListeners.add(dragEventListener)
+ displayController.addDisplayWindowListener(this)
}
override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
@@ -164,7 +170,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
)
- val t = transactionSupplier.get()
+ val t = transactionSupplier()
val startDisplayLayout = displayController.getDisplayLayout(startDisplayId)
val currentDisplayLayout = displayController.getDisplayLayout(displayId)
@@ -196,7 +202,13 @@ class MultiDisplayVeiledResizeTaskPositioner(
)
)
- // TODO(b/383069173): Render drag indicator(s)
+ multiDisplayDragMoveIndicatorController.onDragMove(
+ boundsDp,
+ startDisplayId,
+ desktopWindowDecoration.mTaskInfo,
+ displayIds,
+ transactionSupplier,
+ )
t.setPosition(
desktopWindowDecoration.leash,
@@ -267,7 +279,10 @@ class MultiDisplayVeiledResizeTaskPositioner(
)
)
- // TODO(b/383069173): Clear drag indicator(s)
+ multiDisplayDragMoveIndicatorController.onDragEnd(
+ desktopWindowDecoration.mTaskInfo.taskId,
+ transactionSupplier,
+ )
}
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
@@ -348,6 +363,14 @@ class MultiDisplayVeiledResizeTaskPositioner(
dragEventListeners.remove(dragEventListener)
}
+ override fun onTopologyChanged(topology: DisplayTopology) {
+ // TODO: b/383069173 - Cancel window drag when topology changes happen during drag.
+
+ displayIds.clear()
+ val displayBounds = topology.getAbsoluteBounds()
+ displayIds.addAll(List(displayBounds.size()) { displayBounds.keyAt(it) })
+ }
+
companion object {
// Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
// timing out in the middle of a resize or drag action.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt
new file mode 100644
index 000000000000..abd238847519
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.graphics.RectF
+import android.testing.TestableResources
+import android.view.Display
+import android.view.SurfaceControl
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import java.util.function.Supplier
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for [MultiDisplayDragMoveIndicatorController].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:MultiDisplayDragMoveIndicatorControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
+ private val displayController = mock<DisplayController>()
+ private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
+ private val indicatorSurfaceFactory = mock<MultiDisplayDragMoveIndicatorSurface.Factory>()
+ private val indicatorSurface0 = mock<MultiDisplayDragMoveIndicatorSurface>()
+ private val indicatorSurface1 = mock<MultiDisplayDragMoveIndicatorSurface>()
+ private val transaction = mock<SurfaceControl.Transaction>()
+ private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>()
+ private val taskInfo = mock<RunningTaskInfo>()
+ private val display0 = mock<Display>()
+ private val display1 = mock<Display>()
+
+ private lateinit var resources: TestableResources
+ private val executor = TestShellExecutor()
+
+ private lateinit var controller: MultiDisplayDragMoveIndicatorController
+
+ @Before
+ fun setUp() {
+ resources = mContext.getOrCreateTestableResources()
+ val resourceConfiguration = Configuration()
+ resourceConfiguration.uiMode = 0
+ resources.overrideConfiguration(resourceConfiguration)
+
+ controller =
+ MultiDisplayDragMoveIndicatorController(
+ displayController,
+ rootTaskDisplayAreaOrganizer,
+ indicatorSurfaceFactory,
+ executor,
+ )
+
+ val spyDisplayLayout0 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0,
+ MultiDisplayTestUtil.DISPLAY_DPI_0,
+ resources.resources,
+ )
+ val spyDisplayLayout1 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
+ MultiDisplayTestUtil.DISPLAY_DPI_1,
+ resources.resources,
+ )
+
+ taskInfo.taskId = TASK_ID
+ whenever(displayController.getDisplayLayout(0)).thenReturn(spyDisplayLayout0)
+ whenever(displayController.getDisplayLayout(1)).thenReturn(spyDisplayLayout1)
+ whenever(displayController.getDisplay(0)).thenReturn(display0)
+ whenever(displayController.getDisplay(1)).thenReturn(display1)
+ whenever(indicatorSurfaceFactory.create(taskInfo, display0)).thenReturn(indicatorSurface0)
+ whenever(indicatorSurfaceFactory.create(taskInfo, display1)).thenReturn(indicatorSurface1)
+ whenever(transactionSupplier.get()).thenReturn(transaction)
+ }
+
+ @Test
+ fun onDrag_boundsNotIntersectWithDisplay_noIndicator() {
+ controller.onDragMove(
+ RectF(2000f, 2000f, 2100f, 2200f), // not intersect with any display
+ startDisplayId = 0,
+ taskInfo,
+ displayIds = setOf(0, 1),
+ ) { transaction }
+ executor.flushAll()
+
+ verify(indicatorSurfaceFactory, never()).create(any(), any())
+ }
+
+ @Test
+ fun onDrag_boundsIntersectWithStartDisplay_noIndicator() {
+ controller.onDragMove(
+ RectF(100f, 100f, 200f, 200f), // intersect with display 0
+ startDisplayId = 0,
+ taskInfo,
+ displayIds = setOf(0, 1),
+ ) { transaction }
+ executor.flushAll()
+
+ verify(indicatorSurfaceFactory, never()).create(any(), any())
+ }
+
+ @Test
+ fun onDrag_boundsIntersectWithNonStartDisplay_showAndDisposeIndicator() {
+ controller.onDragMove(
+ RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1
+ startDisplayId = 0,
+ taskInfo,
+ displayIds = setOf(0, 1),
+ ) { transaction }
+ executor.flushAll()
+
+ verify(indicatorSurfaceFactory, times(1)).create(taskInfo, display1)
+ verify(indicatorSurface1, times(1))
+ .show(transaction, taskInfo, rootTaskDisplayAreaOrganizer, 1, Rect(0, 1800, 200, 2400))
+
+ controller.onDragMove(
+ RectF(2000f, 2000f, 2100f, 2200f), // not intersect with display 1
+ startDisplayId = 0,
+ taskInfo,
+ displayIds = setOf(0, 1)
+ ) { transaction }
+ while (executor.callbacks.isNotEmpty()) {
+ executor.flushAll()
+ }
+
+ verify(indicatorSurface1, times(1))
+ .relayout(any(), eq(transaction), shouldBeVisible = eq(false))
+
+ controller.onDragEnd(TASK_ID, { transaction })
+ while (executor.callbacks.isNotEmpty()) {
+ executor.flushAll()
+ }
+
+ verify(indicatorSurface1, times(1)).disposeSurface(transaction)
+ }
+
+ companion object {
+ private const val TASK_ID = 10
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index 0d5741fccbcc..8ad54f5a0bb4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -16,51 +16,28 @@
package com.android.wm.shell.desktopmode
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
-import android.content.ContentResolver
-import android.os.Binder
import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
-import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
-import android.view.IWindowManager
-import android.view.WindowManager.TRANSIT_CHANGE
-import android.window.DisplayAreaInfo
-import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
-import com.android.wm.shell.MockToken
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.transition.Transitions
-import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
-import org.mockito.Mockito.anyInt
import org.mockito.Mockito.spy
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -73,27 +50,18 @@ import org.mockito.quality.Strictness
@RunWith(AndroidTestingRunner::class)
class DesktopDisplayEventHandlerTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
- @Mock lateinit var transitions: Transitions
@Mock lateinit var displayController: DisplayController
- @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock private lateinit var mockWindowManager: IWindowManager
@Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
@Mock private lateinit var mockDesktopRepository: DesktopRepository
@Mock private lateinit var mockDesktopTasksController: DesktopTasksController
- @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
private lateinit var handler: DesktopDisplayEventHandler
private val onDisplaysChangedListenerCaptor = argumentCaptor<OnDisplaysChangedListener>()
- private val runningTasks = mutableListOf<RunningTaskInfo>()
private val externalDisplayId = 100
- private val freeformTask =
- TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()
- private val fullscreenTask =
- TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
- private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
@Before
fun setUp() {
@@ -105,24 +73,15 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
shellInit = spy(ShellInit(testExecutor))
whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
- whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
- .thenReturn(defaultTDA)
handler =
DesktopDisplayEventHandler(
context,
shellInit,
- transitions,
displayController,
- rootTaskDisplayAreaOrganizer,
- mockWindowManager,
mockDesktopUserRepositories,
mockDesktopTasksController,
- shellTaskOrganizer,
+ desktopDisplayModeController,
)
- whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
- runningTasks.add(freeformTask)
- runningTasks.add(fullscreenTask)
shellInit.init()
verify(displayController)
.addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture())
@@ -133,65 +92,6 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
mockitoSession.finishMocking()
}
- private fun testDisplayWindowingModeSwitch(
- defaultWindowingMode: Int,
- extendedDisplayEnabled: Boolean,
- expectTransition: Boolean,
- ) {
- defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
- whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
- val settingsSession =
- ExtendedDisplaySettingsSession(
- context.contentResolver,
- if (extendedDisplayEnabled) 1 else 0,
- )
-
- settingsSession.use {
- connectExternalDisplay()
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- disconnectExternalDisplay()
-
- if (expectTransition) {
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(2))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
- .isEqualTo(defaultWindowingMode)
- } else {
- verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
- }
- }
- }
-
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = false,
- expectTransition = false,
- )
- }
-
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = true,
- expectTransition = true,
- )
- }
-
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FREEFORM,
- extendedDisplayEnabled = true,
- expectTransition = false,
- )
- }
-
@Test
fun testDisplayAdded_supportsDesks_createsDesk() {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -231,70 +131,14 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
}
@Test
- fun displayWindowingModeSwitch_existingTasksOnConnected() {
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
- WINDOWING_MODE_FULLSCREEN
- }
-
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- connectExternalDisplay()
-
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- }
- }
-
- @Test
- fun displayWindowingModeSwitch_existingTasksOnDisconnected() {
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
- WINDOWING_MODE_FULLSCREEN
- }
-
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- disconnectExternalDisplay()
-
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
- }
-
- private fun connectExternalDisplay() {
- whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
+ fun testConnectExternalDisplay() {
onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
+ verify(desktopDisplayModeController).refreshDisplayWindowingMode()
}
- private fun disconnectExternalDisplay() {
- whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
- .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ @Test
+ fun testDisconnectExternalDisplay() {
onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
- }
-
- private class ExtendedDisplaySettingsSession(
- private val contentResolver: ContentResolver,
- private val overrideValue: Int,
- ) : AutoCloseable {
- private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
- private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
-
- init {
- Settings.Global.putInt(contentResolver, settingName, overrideValue)
- }
-
- override fun close() {
- Settings.Global.putInt(contentResolver, settingName, initialValue)
- }
+ verify(desktopDisplayModeController).refreshDisplayWindowingMode()
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
new file mode 100644
index 000000000000..0ff7230f6e0c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.content.ContentResolver
+import android.os.Binder
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [DesktopDisplayModeController]
+ *
+ * Usage: atest WMShellUnitTests:DesktopDisplayModeControllerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopDisplayModeControllerTest : ShellTestCase() {
+ private val transitions = mock<Transitions>()
+ private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
+ private val mockWindowManager = mock<IWindowManager>()
+ private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+ private val desktopWallpaperActivityTokenProvider =
+ mock<DesktopWallpaperActivityTokenProvider>()
+
+ private lateinit var controller: DesktopDisplayModeController
+
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val freeformTask =
+ TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()
+ private val fullscreenTask =
+ TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
+ private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ private val wallpaperToken = MockToken().token()
+
+ @Before
+ fun setUp() {
+ whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder())
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultTDA)
+ controller =
+ DesktopDisplayModeController(
+ context,
+ transitions,
+ rootTaskDisplayAreaOrganizer,
+ mockWindowManager,
+ shellTaskOrganizer,
+ desktopWallpaperActivityTokenProvider,
+ )
+ runningTasks.add(freeformTask)
+ runningTasks.add(fullscreenTask)
+ whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks))
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
+ }
+
+ private fun testDisplayWindowingModeSwitch(
+ defaultWindowingMode: Int,
+ extendedDisplayEnabled: Boolean,
+ expectTransition: Boolean,
+ ) {
+ defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode)
+ val settingsSession =
+ ExtendedDisplaySettingsSession(
+ context.contentResolver,
+ if (extendedDisplayEnabled) 1 else 0,
+ )
+
+ settingsSession.use {
+ connectExternalDisplay()
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ disconnectExternalDisplay()
+
+ if (expectTransition) {
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(2))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(defaultWindowingMode)
+ assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ } else {
+ verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
+ }
+ }
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = false,
+ expectTransition = false,
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = true,
+ expectTransition = true,
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ extendedDisplayEnabled = true,
+ expectTransition = false,
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitch_existingTasksOnConnected() {
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
+
+ ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
+ connectExternalDisplay()
+
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+ }
+
+ @Test
+ fun displayWindowingModeSwitch_existingTasksOnDisconnected() {
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
+ WINDOWING_MODE_FULLSCREEN
+ }
+
+ ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
+ disconnectExternalDisplay()
+
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+ }
+
+ private fun connectExternalDisplay() {
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, EXTERNAL_DISPLAY_ID))
+ controller.refreshDisplayWindowingMode()
+ }
+
+ private fun disconnectExternalDisplay() {
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ controller.refreshDisplayWindowingMode()
+ }
+
+ private class ExtendedDisplaySettingsSession(
+ private val contentResolver: ContentResolver,
+ private val overrideValue: Int,
+ ) : AutoCloseable {
+ private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+ private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+ init {
+ Settings.Global.putInt(contentResolver, settingName, overrideValue)
+ }
+
+ override fun close() {
+ Settings.Global.putInt(contentResolver, settingName, initialValue)
+ }
+ }
+
+ private companion object {
+ const val EXTERNAL_DISPLAY_ID = 100
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index fd5e567f69ed..c8025a94e453 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -64,7 +64,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.os.IResultReceiver;
-import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -73,6 +72,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.shared.R;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index f69bf34ea3f7..88c6e499b869 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -16,13 +16,16 @@
package com.android.wm.shell.shared.desktopmode
+import android.Manifest.permission.SYSTEM_ALERT_WINDOW
import android.app.TaskInfo
import android.compat.testing.PlatformCompatChangeRule
import android.content.ComponentName
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Process
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -39,7 +42,9 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -55,6 +60,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
private val packageManager: PackageManager = mock()
private val homeActivities = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
+ private val baseActivityTest = ComponentName("com.test.dummypackage", "TestClass")
@Before
fun setUp() {
@@ -64,6 +70,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
@@ -71,10 +78,39 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
+ baseActivity = baseActivityTest
}))
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
+ fun testIsTopActivityExemptWithPermission_onlyTransparentActivitiesInStack() {
+ allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW))
+ assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ baseActivity = baseActivityTest
+ }))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
+ fun testIsTopActivityExemptWithNoPermission_onlyTransparentActivitiesInStack() {
+ allowOverlayPermission(arrayOf())
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ baseActivity = baseActivityTest
+ }))
+ }
+
+ @Test
fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() {
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
@@ -219,4 +255,15 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
}
}
}
+
+ fun allowOverlayPermission(permissions: Array<String>) {
+ val packageInfo = mock<PackageInfo>()
+ packageInfo.requestedPermissions = permissions
+ whenever(
+ packageManager.getPackageInfo(
+ anyString(),
+ eq(PackageManager.GET_PERMISSIONS)
+ )
+ ).thenReturn(packageInfo)
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 8cccdb2b6120..81dfaed56b6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -52,6 +52,7 @@ import com.android.wm.shell.common.DisplayChangeController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
@@ -138,6 +139,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>()
protected val mockActivityOrientationChangeHandler =
mock<DesktopActivityOrientationChangeHandler>()
+ protected val mockMultiDisplayDragMoveIndicatorController =
+ mock<MultiDisplayDragMoveIndicatorController>()
protected val mockInputManager = mock<InputManager>()
private val mockTaskPositionerFactory =
mock<DesktopModeWindowDecorViewModel.TaskPositionerFactory>()
@@ -229,6 +232,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockRecentsTransitionHandler,
desktopModeCompatPolicy,
mockTilingWindowDecoration,
+ mockMultiDisplayDragMoveIndicatorController,
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -243,6 +247,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
any(),
any(),
any(),
+ any(),
any()
)
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index 937938df82c8..a6b077037b86 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -41,6 +41,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
import com.android.wm.shell.common.MultiDisplayTestUtil
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
@@ -62,8 +63,8 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
/**
@@ -93,7 +94,8 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
@Mock private lateinit var mockTransitions: Transitions
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockSurfaceControl: SurfaceControl
-
+ @Mock private lateinit var mockMultiDisplayDragMoveIndicatorController:
+ MultiDisplayDragMoveIndicatorController
private lateinit var resources: TestableResources
private lateinit var spyDisplayLayout0: DisplayLayout
private lateinit var spyDisplayLayout1: DisplayLayout
@@ -170,10 +172,11 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration,
mockDisplayController,
mockDragEventListener,
- mockTransactionFactory,
+ { mockTransaction },
mockTransitions,
mockInteractionJankMonitor,
mainHandler,
+ mockMultiDisplayDragMoveIndicatorController,
)
}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index f5e10d94452f..7a51c20f7672 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -44,6 +44,9 @@ namespace android {
namespace {
+constexpr int32_t kDefaultDisplayId = 0;
+constexpr int32_t kDefaultDeviceId = 0;
+
using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;
/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(),
@@ -61,7 +64,7 @@ base::expected<EntryValue, IOError> GetEntryValue(
return table_entry->value();
}
-} // namespace
+} // namespace
struct FindEntryResult {
// The cookie representing the ApkAssets in which the value resides.
@@ -99,14 +102,15 @@ struct Theme::Entry {
Res_value value;
};
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
+ : display_id_(kDefaultDisplayId), device_id_(kDefaultDeviceId) {
configurations_.push_back(configuration);
// Don't invalidate caches here as there's nothing cached yet.
SetApkAssets(apk_assets, false);
}
-AssetManager2::AssetManager2() {
+AssetManager2::AssetManager2() : display_id_(kDefaultDisplayId), device_id_(kDefaultDeviceId) {
configurations_.emplace_back();
}
@@ -172,8 +176,7 @@ void AssetManager2::BuildDynamicRefTable(ApkAssetsList apk_assets) {
// to take effect.
auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
if (iter == target_assets_package_ids.end()) {
- LOG(INFO) << "failed to find target package for overlay "
- << loaded_idmap->OverlayApkPath();
+ LOG(INFO) << "failed to find target package for overlay " << loaded_idmap->OverlayApkPath();
} else {
uint8_t target_package_id = iter->second;
@@ -189,10 +192,11 @@ void AssetManager2::BuildDynamicRefTable(ApkAssetsList apk_assets) {
<< " assigned package group";
PackageGroup& target_package_group = package_groups_[target_idx];
- target_package_group.overlays_.push_back(
- ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
- overlay_ref_table.get()),
- apk_assets_cookies[apk_assets]});
+ target_package_group.overlays_.push_back(ConfiguredOverlay{
+ loaded_idmap->GetTargetResourcesMap(target_package_id, overlay_ref_table.get()),
+ apk_assets_cookies[apk_assets],
+ IsAnyOverlayConstraintSatisfied(loaded_idmap->GetConstraints())
+ });
}
}
@@ -291,7 +295,7 @@ void AssetManager2::DumpToLog() const {
}
LOG(INFO) << "Package ID map: " << list;
- for (const auto& package_group: package_groups_) {
+ for (const auto& package_group : package_groups_) {
list = "";
for (const auto& package : package_group.packages_) {
const LoadedPackage* loaded_package = package.loaded_package_;
@@ -347,7 +351,6 @@ std::shared_ptr<const DynamicRefTable> AssetManager2::GetDynamicRefTableForCooki
const std::unordered_map<std::string, std::string>*
AssetManager2::GetOverlayableMapForPackage(uint32_t package_id) const {
-
if (package_id >= package_ids_.size()) {
return nullptr;
}
@@ -462,6 +465,28 @@ void AssetManager2::SetConfigurations(std::span<const ResTable_config> configura
}
}
+void AssetManager2::SetOverlayConstraints(int32_t display_id, int32_t device_id) {
+ bool changed = false;
+ if (display_id_ != display_id) {
+ display_id_ = display_id;
+ changed = true;
+ }
+ if (device_id_ != device_id) {
+ device_id_ = device_id;
+ changed = true;
+ }
+ if (changed) {
+ // Enable/disable overlays based on current constraints
+ for (PackageGroup& group : package_groups_) {
+ for (auto &overlay: group.overlays_) {
+ overlay.enabled = IsAnyOverlayConstraintSatisfied(
+ overlay.overlay_res_maps_.GetConstraints());
+ }
+ }
+ InvalidateCaches(static_cast<uint32_t>(-1));
+ }
+}
+
std::set<AssetManager2::ApkAssetsPtr> AssetManager2::GetNonSystemOverlays() const {
std::set<ApkAssetsPtr> non_system_overlays;
for (const PackageGroup& package_group : package_groups_) {
@@ -475,6 +500,8 @@ std::set<AssetManager2::ApkAssetsPtr> AssetManager2::GetNonSystemOverlays() cons
if (!found_system_package) {
auto op = StartOperation();
+ // Return all overlays, including the disabled ones as this is used for static info
+ // collection only.
for (const ConfiguredOverlay& overlay : package_group.overlays_) {
if (const auto& asset = GetApkAssets(overlay.cookie)) {
non_system_overlays.insert(std::move(asset));
@@ -651,7 +678,6 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
auto op = StartOperation();
-
// Retrieve the package group from the package id of the resource id.
if (UNLIKELY(!is_valid_resid(resid))) {
LOG(ERROR) << base::StringPrintf("Invalid resource ID 0x%08x.", resid);
@@ -672,7 +698,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
std::optional<FindEntryResult> final_result;
bool final_has_locale = false;
bool final_overlaid = false;
- for (auto & config : configurations_) {
+ for (auto& config : configurations_) {
// Might use this if density_override != 0.
ResTable_config density_override_config;
@@ -698,7 +724,8 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
}
if (!assets->IsLoader()) {
for (const auto& id_map : package_group.overlays_) {
- auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+ auto overlay_entry = id_map.enabled ?
+ id_map.overlay_res_maps_.Lookup(resid) : IdmapResMap::Result();
if (!overlay_entry) {
// No id map entry exists for this target resource.
continue;
@@ -708,7 +735,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
ConfigDescription best_frro_config;
Res_value best_frro_value;
bool frro_found = false;
- for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+ for (const auto& [config, value] : overlay_entry.GetInlineValue()) {
if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
&& config.match(*desired_config)) {
frro_found = true;
@@ -1011,7 +1038,7 @@ std::string AssetManager2::GetLastResourceResolution() const {
resid, resource_name_string.c_str(), conf.toString().c_str());
char str[40];
str[0] = '\0';
- for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
+ for (auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
iter->getBcp47Locale(str);
log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
}
@@ -1504,7 +1531,7 @@ void AssetManager2::RebuildFilterList() {
package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
- for (auto & config : configurations_) {
+ for (auto& config : configurations_) {
if (type_entry.config.match(config)) {
if (!group) {
group = &package.filtered_configs_.editItemAt(type_id - 1);
@@ -1521,6 +1548,27 @@ void AssetManager2::RebuildFilterList() {
}
}
+bool AssetManager2::IsAnyOverlayConstraintSatisfied(const Idmap_constraints& constraints) const {
+ if (constraints.constraint_count == 0) {
+ // There are no constraints, return true.
+ return true;
+ }
+
+ for (uint32_t i = 0; i < constraints.constraint_count; i++) {
+ auto constraint = constraints.constraint_entries[i];
+ if (constraint.constraint_type == kOverlayConstraintTypeDisplayId &&
+ constraint.constraint_value == display_id_) {
+ return true;
+ }
+ if (constraint.constraint_type == kOverlayConstraintTypeDeviceId &&
+ constraint.constraint_value == device_id_) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
void AssetManager2::InvalidateCaches(uint32_t diff) {
cached_resolved_values_.clear();
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index f0ef97e5bdcc..8d1de1af56d2 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -56,13 +56,6 @@ struct Idmap_header {
// without having to read/store each header entry separately.
};
-struct Idmap_constraint {
- // Constraint type can be TYPE_DISPLAY_ID or TYP_DEVICE_ID, please refer
- // to ConstraintType in OverlayConstraint.java
- uint32_t constraint_type;
- uint32_t constraint_value;
-};
-
struct Idmap_data_header {
uint32_t target_entry_count;
uint32_t target_inline_entry_count;
@@ -148,12 +141,13 @@ status_t OverlayDynamicRefTable::lookupResourceIdNoRewrite(uint32_t* resId) cons
return DynamicRefTable::lookupResourceId(resId);
}
-IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, Idmap_target_entries entries,
- Idmap_target_inline_entries inline_entries,
+IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, const Idmap_constraints& constraints,
+ Idmap_target_entries entries, Idmap_target_inline_entries inline_entries,
const Idmap_target_entry_inline_value* inline_entry_values,
const ConfigDescription* configs, uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table)
: data_header_(data_header),
+ constraints_(constraints),
entries_(entries),
inline_entries_(inline_entries),
inline_entry_values_(inline_entry_values),
@@ -254,7 +248,7 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size
}
return std::string_view(data, *len);
}
-} // namespace
+} // namespace
// O_PATH is a lightweight way of creating an FD, only exists on Linux
#ifndef O_PATH
@@ -262,9 +256,7 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size
#endif
LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* header,
- const Idmap_constraint* constraints,
- uint32_t constraints_count,
- const Idmap_data_header* data_header,
+ const Idmap_data_header* data_header, const Idmap_constraints& constraints,
Idmap_target_entries target_entries,
Idmap_target_inline_entries target_inline_entries,
const Idmap_target_entry_inline_value* inline_entry_values,
@@ -272,9 +264,8 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head
std::unique_ptr<ResStringPool>&& string_pool,
std::string_view overlay_apk_path, std::string_view target_apk_path)
: header_(header),
- constraints_(constraints),
- constraints_count_(constraints_count),
data_header_(data_header),
+ constraints_(constraints),
target_entries_(target_entries),
target_inline_entries_(target_inline_entries),
inline_entry_values_(inline_entry_values),
@@ -328,16 +319,20 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
return {};
}
- auto constraints_count = ReadType<uint32_t>(&data_ptr, &data_size, "constraints count");
- if (!constraints_count) {
+ auto constraint_count = ReadType<uint32_t>(&data_ptr, &data_size, "constraint count");
+ if (!constraint_count) {
+ LOG(ERROR) << "idmap doesn't have constraint count";
return {};
}
- auto constraints = *constraints_count > 0 ?
- ReadType<Idmap_constraint>(&data_ptr, &data_size, "constraints", *constraints_count)
+ auto constraint_entries = *constraint_count > 0 ?
+ ReadType<Idmap_constraint>(&data_ptr, &data_size, "constraints", dtohl(*constraint_count))
: nullptr;
- if (*constraints_count > 0 && !constraints) {
+ if (*constraint_count > 0 && !constraint_entries) {
+ LOG(ERROR) << "no constraint entries in idmap with non-zero constraints";
return {};
}
+ Idmap_constraints constraints{.constraint_count = *constraint_count,
+ .constraint_entries = constraint_entries};
// Parse the idmap data blocks. Currently idmap2 can only generate one data block.
auto data_header = ReadType<Idmap_data_header>(&data_ptr, &data_size, "data header");
@@ -405,10 +400,9 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
// Can't use make_unique because LoadedIdmap constructor is private.
return std::unique_ptr<LoadedIdmap>(
- new LoadedIdmap(std::string(idmap_path), header, constraints, *constraints_count,
- data_header, target_entries, target_inline_entries,
- target_inline_entry_values,configurations, overlay_entries,
- std::move(idmap_string_pool),*overlay_path, *target_path));
+ new LoadedIdmap(std::string(idmap_path), header, data_header, constraints, target_entries,
+ target_inline_entries, target_inline_entry_values, configurations,
+ overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
}
UpToDate LoadedIdmap::IsUpToDate() const {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 0fdeefa09e26..a47fe6a7f50d 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -171,6 +171,8 @@ class AssetManager2 {
default_locale_ = default_locale;
}
+ void SetOverlayConstraints(int32_t display_id, int32_t device_id);
+
// Returns all configurations for which there are resources defined, or an I/O error if reading
// resource data failed.
//
@@ -389,6 +391,9 @@ class AssetManager2 {
// The cookie of the overlay assets.
ApkAssetsCookie cookie;
+
+ // Enable/disable status of the overlay based on current constraints of AssetManager.
+ bool enabled;
};
// Represents a logical package, which can be made up of many individual packages. Each package
@@ -457,6 +462,8 @@ class AssetManager2 {
// promoted apk assets when the last operation ends.
void FinishOperation() const;
+ bool IsAnyOverlayConstraintSatisfied(const Idmap_constraints& constraints) const;
+
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
// The second pair element is the promoted version of the assets, that is held for the duration
@@ -480,6 +487,9 @@ class AssetManager2 {
// may need to be purged.
ftl::SmallVector<ResTable_config, 1> configurations_;
+ int32_t display_id_;
+ int32_t device_id_;
+
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
mutable std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 0c0856315d8f..939b62462560 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -59,13 +59,25 @@ inline UpToDate fromBool(bool value) {
class LoadedIdmap;
class IdmapResMap;
struct Idmap_header;
-struct Idmap_constraint;
+struct Idmap_constraints;
struct Idmap_data_header;
-struct Idmap_target_entry;
struct Idmap_target_entry_inline;
struct Idmap_target_entry_inline_value;
-struct Idmap_overlay_entry;
+// LINT.IfChange
+constexpr int32_t kOverlayConstraintTypeDisplayId = 0;
+constexpr int32_t kOverlayConstraintTypeDeviceId = 1;
+// LINT.ThenChange(../../../../core/java/android/content/om/OverlayConstraint.java)
+
+struct Idmap_constraint {
+ // Constraint type can be kOverlayConstraintTypeDisplayId or kOverlayConstraintTypeDeviceId
+ const uint32_t constraint_type;
+ const uint32_t constraint_value;
+};
+struct Idmap_constraints {
+ const uint32_t constraint_count = 0;
+ const Idmap_constraint* constraint_entries = nullptr;
+};
struct Idmap_target_entries {
const uint32_t* target_id = nullptr;
const uint32_t* overlay_id = nullptr;
@@ -169,14 +181,19 @@ class IdmapResMap {
return overlay_ref_table_;
}
+ inline Idmap_constraints GetConstraints() const {
+ return constraints_;
+ }
+
private:
- explicit IdmapResMap(const Idmap_data_header* data_header, Idmap_target_entries entries,
- Idmap_target_inline_entries inline_entries,
+ explicit IdmapResMap(const Idmap_data_header* data_header, const Idmap_constraints& constraints,
+ Idmap_target_entries entries, Idmap_target_inline_entries inline_entries,
const Idmap_target_entry_inline_value* inline_entry_values,
const ConfigDescription* configs, uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table);
const Idmap_data_header* data_header_;
+ Idmap_constraints constraints_;
Idmap_target_entries entries_;
Idmap_target_inline_entries inline_entries_;
const Idmap_target_entry_inline_value* inline_entry_values_;
@@ -210,8 +227,9 @@ class LoadedIdmap {
// Returns a mapping from target resource ids to overlay values.
IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table) const {
- return IdmapResMap(data_header_, target_entries_, target_inline_entries_, inline_entry_values_,
- configurations_, target_assigned_package_id, overlay_ref_table);
+ return IdmapResMap(data_header_, constraints_, target_entries_, target_inline_entries_,
+ inline_entry_values_, configurations_, target_assigned_package_id,
+ overlay_ref_table);
}
// Returns a dynamic reference table for a loaded overlay package.
@@ -223,14 +241,17 @@ class LoadedIdmap {
// LoadedIdmap.
UpToDate IsUpToDate() const;
+ inline const Idmap_constraints GetConstraints() const {
+ return constraints_;
+ }
+
protected:
// Exposed as protected so that tests can subclass and mock this class out.
LoadedIdmap() = default;
const Idmap_header* header_;
- const Idmap_constraint* constraints_;
- uint32_t constraints_count_;
const Idmap_data_header* data_header_;
+ Idmap_constraints constraints_;
Idmap_target_entries target_entries_;
Idmap_target_inline_entries target_inline_entries_;
const Idmap_target_entry_inline_value* inline_entry_values_;
@@ -247,9 +268,7 @@ class LoadedIdmap {
DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
explicit LoadedIdmap(const std::string& idmap_path, const Idmap_header* header,
- const Idmap_constraint* constraints,
- uint32_t constraints_count,
- const Idmap_data_header* data_header,
+ const Idmap_data_header* data_header, const Idmap_constraints& constraints,
Idmap_target_entries target_entries,
Idmap_target_inline_entries target_inline_entries,
const Idmap_target_entry_inline_value* inline_entry_values_,
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 88981eac9bb5..1cb540b09fb3 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -481,9 +481,9 @@ public final class MediaRoute2Info implements Parcelable {
/**
* Indicates that a route supports routing playback to remote routes through control commands.
*
- * <p>This type of routing does not affect affect this system's audio or video, but instead
- * relies on the device that corresponds to this route to fetch and play the media. It also
- * requires the media app to take care of initializing and controlling playback.
+ * <p>This type of routing does not affect this system's audio or video, but instead relies on
+ * the device that corresponds to this route to fetch and play the media. It also requires the
+ * media app to take care of initializing and controlling playback.
*/
@FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
public static final int FLAG_ROUTING_TYPE_REMOTE = 1 << 2;
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index e94fb7d9e52b..4ae8daa63e1d 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -348,18 +348,21 @@ public abstract class MediaRoute2ProviderService extends Service {
* <p>This method must only be called as the result of a prior call to {@link
* #onCreateSystemRoutingSession}.
*
+ * <p>This method returns a {@link MediaStreams} instance that holds the media streams to route
+ * as part of the newly created routing session. May be null if system media capture failed, in
+ * which case you can ignore the return value, as you will receive a call to {@link
+ * #onReleaseSession} where you can clean up this session. {@link AudioRecord#startRecording()}
+ * must be called immediately on {@link MediaStreams#getAudioRecord()} after calling this
+ * method, in order to start streaming audio to the receiver.
+ *
* @param requestId the ID of the {@link #onCreateSystemRoutingSession} request which this call
* is in response to.
* @param sessionInfo a {@link RoutingSessionInfo} that describes the newly created routing
* session.
* @param formats the {@link MediaStreamsFormats} that describes the format for the {@link
* MediaStreams} to return.
- * @return a {@link MediaStreams} instance that holds the media streams to route as part of the
- * newly created routing session. May be null if system media capture failed, in which case
- * you can ignore the return value, as you will receive a call to {@link #onReleaseSession}
- * where you can clean up this session. {@link AudioRecord#startRecording()} must be called
- * immediately on {@link MediaStreams#getAudioRecord()} after calling this method, in order
- * to start streaming audio to the receiver.
+ * @return The {@link MediaStreams} to route as part of the new session, or null if system media
+ * capture failed and the result can be ignored.
* @throws IllegalStateException If the provided {@code requestId} doesn't correspond to a
* previous call to {@link #onCreateSystemRoutingSession}.
*/
@@ -1191,8 +1194,8 @@ public abstract class MediaRoute2ProviderService extends Service {
*
* <p>The default value is an empty {@link Bundle}.
*
- * <p>Note that this bundle is not copied, so avoiding mutating the given {@link Bundle}
- * after passing it to this method.
+ * <p>Do not mutate the given {@link Bundle} after passing it to this method. You can
+ * use {@link Bundle#deepCopy()} to keep a mutable copy.
*/
@NonNull
public Builder setExtras(@NonNull Bundle extras) {
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 4fe0b80f3951..275972495206 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -715,47 +715,45 @@ void ASurfaceTransaction_setLuts(ASurfaceTransaction* aSurfaceTransaction,
sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
- int fd = -1;
+ base::unique_fd fd;
std::vector<int32_t> offsets;
std::vector<int32_t> dimensions;
std::vector<int32_t> sizes;
std::vector<int32_t> samplingKeys;
if (luts) {
- std::vector<float> buffer(luts->totalBufferSize);
int32_t count = luts->offsets.size();
offsets = luts->offsets;
dimensions.reserve(count);
sizes.reserve(count);
samplingKeys.reserve(count);
- for (int32_t i = 0; i < count; i++) {
- dimensions.emplace_back(luts->entries[i]->properties.dimension);
- sizes.emplace_back(luts->entries[i]->properties.size);
- samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey);
- std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(),
- buffer.begin() + offsets[i]);
- }
// mmap
- fd = ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float));
+ fd.reset(ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float)));
if (fd < 0) {
LOG_ALWAYS_FATAL("setLuts, ashmem_create_region() failed");
return;
}
- void* ptr = mmap(nullptr, luts->totalBufferSize * sizeof(float), PROT_READ | PROT_WRITE,
- MAP_SHARED, fd, 0);
+ float* ptr = (float*)mmap(nullptr, luts->totalBufferSize * sizeof(float),
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
LOG_ALWAYS_FATAL("setLuts, Failed to map the shared memory");
return;
}
- memcpy(ptr, buffer.data(), luts->totalBufferSize * sizeof(float));
+ for (int32_t i = 0; i < count; i++) {
+ dimensions.emplace_back(luts->entries[i]->properties.dimension);
+ sizes.emplace_back(luts->entries[i]->properties.size);
+ samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey);
+ std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(),
+ ptr + offsets[i]);
+ }
+
munmap(ptr, luts->totalBufferSize * sizeof(float));
}
- transaction->setLuts(surfaceControl, base::unique_fd(fd), offsets, dimensions, sizes,
- samplingKeys);
+ transaction->setLuts(surfaceControl, std::move(fd), offsets, dimensions, sizes, samplingKeys);
}
void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index d6e19a6193fd..a75aeaff0c48 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -104,6 +104,7 @@ fun WearApp(
scrollable(Screen.MultipleCredentialsScreenFlatten.route) {
MultiCredentialsFlattenScreen(
credentialSelectorUiState = (remember { uiState } as MultipleEntry),
+ columnState = it.columnState,
flowEngine = flowEngine,
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index 932b3456bfb8..96cadab3916a 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -36,7 +36,7 @@ import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.CredentialsScreenChipSpacer
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
-import com.google.android.horologist.compose.layout.rememberColumnState
+import com.google.android.horologist.compose.layout.ScalingLazyColumnState
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
import androidx.compose.ui.text.style.TextAlign
import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
@@ -45,6 +45,7 @@ import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
* Screen that shows multiple credentials to select from, grouped by accounts
*
* @param credentialSelectorUiState The app bar view model.
+ * @param columnState ScalingLazyColumn configuration to be be applied
* @param modifier styling for composable
* @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@@ -52,15 +53,14 @@ import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
@Composable
fun MultiCredentialsFlattenScreen(
credentialSelectorUiState: MultipleEntry,
+ columnState: ScalingLazyColumnState,
flowEngine: FlowEngine,
) {
val selectEntry = flowEngine.getEntrySelector()
Row {
Spacer(Modifier.weight(0.052f)) // 5.2% side margin
ScalingLazyColumn(
- columnState = rememberColumnState(
- ScalingLazyColumnDefaults.belowTimeText(horizontalAlignment = Alignment.Start),
- ),
+ columnState = columnState,
modifier = Modifier.weight(0.896f).fillMaxSize(), // 5.2% side margin
) {
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index bd892637a5eb..853d1ad0dc94 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -152,34 +152,6 @@ android_app {
}
//##########################################################
-// Variant: System app upgrade
-
-android_app {
- name: "CtsShimUpgrade",
-
- sdk_version: "current",
- optimize: {
- enabled: false,
- },
- dex_preopt: {
- enabled: false,
- },
-
- manifest: "shim/AndroidManifestUpgrade.xml",
- min_sdk_version: "24",
-}
-
-java_genrule {
- name: "generate_shim_manifest",
- srcs: [
- "shim/AndroidManifest.xml",
- ":CtsShimUpgrade",
- ],
- out: ["AndroidManifest.xml"],
- cmd: "sed -e s/__HASH__/`sha512sum -b $(location :CtsShimUpgrade) | cut -d' ' -f1`/ $(location shim/AndroidManifest.xml) > $(out)",
-}
-
-//##########################################################
// Variant: System app
android_app {
@@ -193,7 +165,7 @@ android_app {
enabled: false,
},
- manifest: ":generate_shim_manifest",
+ manifest: "shim/AndroidManifest.xml",
apex_available: [
"//apex_available:platform",
"com.android.apex.cts.shim.v1",
diff --git a/packages/CtsShim/build/shim/AndroidManifest.xml b/packages/CtsShim/build/shim/AndroidManifest.xml
index 3b8276b66bac..1ffe56c0c76a 100644
--- a/packages/CtsShim/build/shim/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim/AndroidManifest.xml
@@ -17,15 +17,13 @@
<!-- Manifest for the system CTS shim -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.android.cts.ctsshim"
- android:sharedUserId="com.android.cts.ctsshim" >
+ package="com.android.cts.ctsshim" >
- <uses-sdk
- android:minSdkVersion="24"
+ <uses-sdk android:minSdkVersion="24"
android:targetSdkVersion="28" />
<restrict-update
- android:hash="__HASH__" />
+ android:hash="__CAN_NOT_BE_UPDATED__" />
<application
android:hasCode="false"
diff --git a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml b/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml
deleted file mode 100644
index 7f3644a18ad6..000000000000
--- a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<!-- Manifest for the system CTS shim -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.cts.ctsshim" >
-
- <uses-sdk
- android:minSdkVersion="24"
- android:targetSdkVersion="28" />
-
- <application
- android:hasCode="false"
- tools:ignore="AllowBackup,MissingApplicationIcon" />
-</manifest>
-
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index 412ff29aec32..473b7eb07666 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -21,6 +21,13 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<application>
+ <activity
+ android:name=".users.CreateUserActivity"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:finishOnCloseSystemDialogs="true"
+ android:launchMode="singleInstance"
+ android:theme="@style/Theme.Transparent"/>
</application>
</manifest>
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 182daeb45d7c..203a3bf64910 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -31,5 +31,6 @@ android_library {
apex_available: [
"//apex_available:platform",
"com.android.healthfitness",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml
new file mode 100644
index 000000000000..c999de7d99ea
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml
@@ -0,0 +1,157 @@
+<!--
+ ~ 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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="112dp"
+ android:height="112dp"
+ android:viewportHeight="112"
+ android:viewportWidth="112">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="56.5"
+ android:translateY="56.625">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="?android:attr/textColorPrimary"
+ android:fillType="nonZero"
+ android:pathData=" M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:trimPathEnd="1"
+ android:trimPathOffset="0"
+ android:trimPathStart="0" />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:rotation="-90"
+ android:translateX="56"
+ android:translateY="56">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:pathData=" M53.5 0 C53.5,-29.53 29.53,-53.5 0,-53.5 C0,-53.5 0,-53.5 0,-53.5 C-29.53,-53.5 -53.5,-29.53 -53.5,0 C-53.5,0 -53.5,0 -53.5,0 C-53.5,29.53 -29.53,53.5 0,53.5 C0,53.5 0,53.5 0,53.5 C29.53,53.5 53.5,29.53 53.5,0 C53.5,0 53.5,0 53.5,0c "
+ android:strokeAlpha="1"
+ android:strokeColor="?android:attr/textColorTertiary"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="5"
+ android:trimPathEnd="0"
+ android:trimPathOffset="0"
+ android:trimPathStart="0" />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="fillAlpha"
+ android:startOffset="400"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="pathData"
+ android:startOffset="400"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="483"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 14.77,-13.41 14.77,-13.41 C14.77,-13.41 17.59,-10.59 17.59,-10.59 C17.59,-10.59 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="trimPathEnd"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
index b10ef6e77ec7..c448a2d434f8 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
@@ -28,81 +28,104 @@
android:orientation="vertical"
style="@style/Banner.Preference.SettingsLib.Expressive">
- <RelativeLayout
- android:id="@+id/top_row"
+ <FrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:layout_height="wrap_content">
<LinearLayout
+ android:id="@+id/banner_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
android:orientation="vertical">
- <TextView
- android:id="@+id/banner_header"
+
+ <RelativeLayout
+ android:id="@+id/top_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Header.SettingsLib.Expressive"/>
+ android:orientation="horizontal">
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/banner_icon"
- android:layout_width="@dimen/settingslib_expressive_space_small3"
- android:layout_height="@dimen/settingslib_expressive_space_small3"
- android:layout_gravity="center_vertical"
- android:importantForAccessibility="no"
- android:scaleType="fitCenter" />
-
- <TextView
- android:id="@+id/banner_title"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Title.SettingsLib.Expressive" />
- </LinearLayout>
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/banner_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Header.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="@dimen/settingslib_expressive_space_small3"
+ android:layout_height="@dimen/settingslib_expressive_space_small3"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall4"
+ android:importantForAccessibility="no"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/banner_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Title.SettingsLib.Expressive" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/banner_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ </LinearLayout>
+
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/accessibility_banner_message_dismiss"
+ style="@style/Banner.Dismiss.SettingsLib.Expressive" />
+ </RelativeLayout>
<TextView
- android:id="@+id/banner_subtitle"
+ android:id="@+id/banner_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Summary.SettingsLib.Expressive"/>
+
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ android:id="@+id/banner_buttons_frame"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
+ android:orientation="horizontal">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_negative_btn"
+ android:layout_weight="1"
+ style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
+ <Space
+ android:id="@+id/banner_button_space"
+ android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_height="@dimen/settingslib_expressive_space_small1"/>
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_positive_btn"
+ android:layout_weight="1"
+ style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
+ </LinearLayout>
</LinearLayout>
- <ImageButton
- android:id="@+id/banner_dismiss_btn"
+ <TextView
+ android:id="@+id/resolved_banner_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.Dismiss.SettingsLib.Expressive" />
- </RelativeLayout>
-
- <TextView
- android:id="@+id/banner_summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@style/Banner.Summary.SettingsLib.Expressive"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/banner_buttons_frame"
- android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
- android:orientation="horizontal">
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/banner_negative_btn"
- android:layout_weight="1"
- style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
- <Space
- android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
- android:layout_height="@dimen/settingslib_expressive_space_small1"/>
- <com.google.android.material.button.MaterialButton
- android:id="@+id/banner_positive_btn"
- android:layout_weight="1"
- style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
- </LinearLayout>
+ android:drawableTop="@drawable/settingslib_resolved_banner_avd"
+ android:visibility="gone"
+ style="@style/Banner.ResolvedText.SettingsLib.Expressive"/>
+ </FrameLayout>
</com.android.settingslib.widget.BannerMessageView>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
index b864311bf9b7..09e07ccef683 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
@@ -33,7 +33,6 @@
<style name="Banner.Title.SettingsLib.Expressive"
parent="">
<item name="android:layout_gravity">start</item>
- <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item>
<item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -73,4 +72,11 @@
parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra">
<item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
</style>
+
+ <style name="Banner.ResolvedText.SettingsLib.Expressive" parent="">
+ <item name="android:layout_gravity">center</item>
+ <item name="android:drawablePadding">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index c82829d6ccea..c90a76a39510 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -35,6 +35,8 @@ import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.preference.Preference;
@@ -43,6 +45,9 @@ import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.banner.R;
import com.google.android.material.button.MaterialButton;
+
+import java.util.Objects;
+
/**
* Banner message is a banner displaying important information (permission request, page error etc),
* and provide actions for user to address. It requires a user action to be dismissed.
@@ -67,13 +72,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
R.color.banner_accent_attention_normal,
R.color.settingslib_banner_button_background_normal);
- // Corresponds to the enum valye of R.attr.attentionLevel
+ // Corresponds to the enum value of R.attr.attentionLevel
private final int mAttrValue;
@ColorRes private final int mBackgroundColorResId;
@ColorRes private final int mAccentColorResId;
@ColorRes private final int mButtonBackgroundColorResId;
- AttentionLevel(int attrValue, @ColorRes int backgroundColorResId,
+ AttentionLevel(
+ int attrValue,
+ @ColorRes int backgroundColorResId,
@ColorRes int accentColorResId,
@ColorRes int buttonBackgroundColorResId) {
mAttrValue = attrValue;
@@ -115,33 +122,38 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
new BannerMessagePreference.DismissButtonInfo();
// Default attention level is High.
- private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
- private CharSequence mSubtitle;
- private CharSequence mHeader;
+ @NonNull private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
+ @Nullable private CharSequence mSubtitle;
+ @Nullable private CharSequence mHeader;
private int mButtonOrientation;
+ @Nullable private ResolutionAnimator.Data mResolutionData;
- public BannerMessagePreference(Context context) {
+ public BannerMessagePreference(@NonNull Context context) {
super(context);
init(context, null /* attrs */);
}
- public BannerMessagePreference(Context context, AttributeSet attrs) {
+ public BannerMessagePreference(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
- public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ public BannerMessagePreference(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
- public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr,
+ public BannerMessagePreference(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
- private void init(Context context, AttributeSet attrs) {
+ private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
setSelectable(false);
int resId = SettingsThemeHelper.isExpressiveTheme(context)
? R.layout.settingslib_expressive_banner_message
@@ -166,7 +178,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
@Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final Context context = getContext();
@@ -193,14 +205,20 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
final ImageView iconView = (ImageView) holder.findViewById(R.id.banner_icon);
if (iconView != null) {
Drawable icon = getIcon();
- iconView.setImageDrawable(
- icon == null
- ? getContext().getDrawable(R.drawable.ic_warning)
- : icon);
- if (mAttentionLevel != AttentionLevel.NORMAL
- && !SettingsThemeHelper.isExpressiveTheme(context)) {
- iconView.setColorFilter(
- new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+
+ if (icon == null && SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setVisibility(View.GONE);
+ } else {
+ iconView.setVisibility(View.VISIBLE);
+ iconView.setImageDrawable(
+ icon == null
+ ? getContext().getDrawable(R.drawable.ic_warning)
+ : icon);
+ if (mAttentionLevel != AttentionLevel.NORMAL
+ && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setColorFilter(
+ new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ }
}
}
@@ -233,8 +251,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
mDismissButtonInfo.setUpButton();
final TextView subtitleView = (TextView) holder.findViewById(R.id.banner_subtitle);
- subtitleView.setText(mSubtitle);
- subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ if (subtitleView != null) {
+ subtitleView.setText(mSubtitle);
+ subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ }
TextView headerView = (TextView) holder.findViewById(R.id.banner_header);
if (headerView != null) {
@@ -268,11 +288,25 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
linearLayout.setOrientation(mButtonOrientation);
}
}
+
+ View buttonSpace = holder.findViewById(R.id.banner_button_space);
+ if (buttonSpace != null) {
+ if (mPositiveButtonInfo.shouldBeVisible() && mNegativeButtonInfo.shouldBeVisible()) {
+ buttonSpace.setVisibility(View.VISIBLE);
+ } else {
+ buttonSpace.setVisibility(View.GONE);
+ }
+ }
+
+ if (mResolutionData != null) {
+ new ResolutionAnimator(mResolutionData, holder).startResolutionAnimation();
+ }
}
/**
- * Set the visibility state of positive button.
+ * Sets the visibility state of the positive button.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonVisible(boolean isVisible) {
if (isVisible != mPositiveButtonInfo.mIsVisible) {
mPositiveButtonInfo.mIsVisible = isVisible;
@@ -282,8 +316,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Set the visibility state of negative button.
+ * Sets the visibility state of the negative button.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonVisible(boolean isVisible) {
if (isVisible != mNegativeButtonInfo.mIsVisible) {
mNegativeButtonInfo.mIsVisible = isVisible;
@@ -293,9 +328,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Set the visibility state of dismiss button.
+ * Sets the visibility state of the dismiss button.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setDismissButtonVisible(boolean isVisible) {
if (isVisible != mDismissButtonInfo.mIsVisible) {
mDismissButtonInfo.mIsVisible = isVisible;
@@ -305,10 +341,35 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when positive button is clicked.
+ * Sets the enabled state of the positive button.
+ */
+ @NonNull
+ public BannerMessagePreference setPositiveButtonEnabled(boolean isEnabled) {
+ if (isEnabled != mPositiveButtonInfo.mIsEnabled) {
+ mPositiveButtonInfo.mIsEnabled = isEnabled;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
+ * Sets the enabled state of the negative button.
+ */
+ @NonNull
+ public BannerMessagePreference setNegativeButtonEnabled(boolean isEnabled) {
+ if (isEnabled != mNegativeButtonInfo.mIsEnabled) {
+ mNegativeButtonInfo.mIsEnabled = isEnabled;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
+ * Registers a callback to be invoked when positive button is clicked.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mPositiveButtonInfo.mListener) {
mPositiveButtonInfo.mListener = listener;
notifyChanged();
@@ -317,10 +378,11 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when negative button is clicked.
+ * Registers a callback to be invoked when negative button is clicked.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mNegativeButtonInfo.mListener) {
mNegativeButtonInfo.mListener = listener;
notifyChanged();
@@ -329,11 +391,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when the dismiss button is clicked.
+ * Registers a callback to be invoked when the dismiss button is clicked.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setDismissButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mDismissButtonInfo.mListener) {
mDismissButtonInfo.mListener = listener;
notifyChanged();
@@ -344,6 +407,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in positive button.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonText(@StringRes int textResId) {
return setPositiveButtonText(getContext().getString(textResId));
}
@@ -351,7 +415,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in positive button.
*/
- public BannerMessagePreference setPositiveButtonText(CharSequence positiveButtonText) {
+ @NonNull
+ public BannerMessagePreference setPositiveButtonText(
+ @Nullable CharSequence positiveButtonText) {
if (!TextUtils.equals(positiveButtonText, mPositiveButtonInfo.mText)) {
mPositiveButtonInfo.mText = positiveButtonText;
notifyChanged();
@@ -362,6 +428,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in negative button.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonText(@StringRes int textResId) {
return setNegativeButtonText(getContext().getString(textResId));
}
@@ -369,7 +436,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in negative button.
*/
- public BannerMessagePreference setNegativeButtonText(CharSequence negativeButtonText) {
+ @NonNull
+ public BannerMessagePreference setNegativeButtonText(
+ @Nullable CharSequence negativeButtonText) {
if (!TextUtils.equals(negativeButtonText, mNegativeButtonInfo.mText)) {
mNegativeButtonInfo.mText = negativeButtonText;
notifyChanged();
@@ -380,8 +449,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets button orientation.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
public BannerMessagePreference setButtonOrientation(int orientation) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+
if (mButtonOrientation != orientation) {
mButtonOrientation = orientation;
notifyChanged();
@@ -393,6 +466,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setSubtitle(@StringRes int textResId) {
return setSubtitle(getContext().getString(textResId));
}
@@ -401,7 +475,8 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
- public BannerMessagePreference setSubtitle(CharSequence subtitle) {
+ @NonNull
+ public BannerMessagePreference setSubtitle(@Nullable CharSequence subtitle) {
if (!TextUtils.equals(subtitle, mSubtitle)) {
mSubtitle = subtitle;
notifyChanged();
@@ -412,7 +487,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the header.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
public BannerMessagePreference setHeader(@StringRes int textResId) {
return setHeader(getContext().getString(textResId));
}
@@ -420,8 +495,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the header.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public BannerMessagePreference setHeader(CharSequence header) {
+ @NonNull
+ public BannerMessagePreference setHeader(@Nullable CharSequence header) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+
if (!TextUtils.equals(header, mHeader)) {
mHeader = header;
notifyChanged();
@@ -430,32 +509,75 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Sets the attention level. This will update the color theme of the preference.
+ * Plays a resolution animation, showing the given message.
*/
- public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) {
- if (attentionLevel == mAttentionLevel) {
+ @NonNull
+ public BannerMessagePreference showResolutionAnimation(
+ @NonNull CharSequence resolutionMessage,
+ @NonNull ResolutionCompletedCallback onCompletionCallback) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
return this;
}
+ ResolutionAnimator.Data resolutionData =
+ new ResolutionAnimator.Data(resolutionMessage, onCompletionCallback);
+ if (!Objects.equals(mResolutionData, resolutionData)) {
+ mResolutionData = resolutionData;
+ notifyChanged();
+ }
+ return this;
+ }
- if (attentionLevel != null) {
- mAttentionLevel = attentionLevel;
+ /**
+ * Removes the resolution animation from this preference.
+ *
+ * <p>Should be called after the resolution animation completes if this preference will be
+ * reused. Otherwise the resolution animation will be played everytime this preference is
+ * displayed.
+ */
+ @NonNull
+ public BannerMessagePreference clearResolutionAnimation() {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+ if (mResolutionData != null) {
+ mResolutionData = null;
notifyChanged();
}
return this;
}
+ /**
+ * Sets the attention level. This will update the color theme of the preference.
+ */
+ @NonNull
+ public BannerMessagePreference setAttentionLevel(@NonNull AttentionLevel attentionLevel) {
+ if (attentionLevel == mAttentionLevel) {
+ return this;
+ }
+
+ mAttentionLevel = attentionLevel;
+ notifyChanged();
+ return this;
+ }
+
static class ButtonInfo {
- private Button mButton;
- private CharSequence mText;
- private View.OnClickListener mListener;
+ @Nullable private Button mButton;
+ @Nullable private CharSequence mText;
+ @Nullable private View.OnClickListener mListener;
private boolean mIsVisible = true;
+ private boolean mIsEnabled = true;
@ColorInt private int mColor;
@ColorInt private int mBackgroundColor;
- private ColorStateList mStrokeColor;
+ @Nullable private ColorStateList mStrokeColor;
void setUpButton() {
+ if (mButton == null) {
+ return;
+ }
+
mButton.setText(mText);
mButton.setOnClickListener(mListener);
+ mButton.setEnabled(mIsEnabled);
MaterialButton btn = null;
if (mButton instanceof MaterialButton) {
@@ -492,11 +614,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
static class DismissButtonInfo {
- private ImageButton mButton;
- private View.OnClickListener mListener;
+ @Nullable private ImageButton mButton;
+ @Nullable private View.OnClickListener mListener;
private boolean mIsVisible = true;
void setUpButton() {
+ if (mButton == null) {
+ return;
+ }
+
mButton.setOnClickListener(mListener);
if (shouldBeVisible()) {
mButton.setVisibility(View.VISIBLE);
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
index ff4e79ddaaa1..eabb6341c3dd 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
@@ -23,6 +23,7 @@ import android.view.TouchDelegate;
import android.view.View;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.widget.preference.banner.R;
@@ -34,22 +35,25 @@ import com.android.settingslib.widget.preference.banner.R;
* {@link BannerMessagePreference} to a {@code PreferenceScreen}.
*/
public class BannerMessageView extends LinearLayout {
- private Rect mTouchTargetForDismissButton;
+ @Nullable private Rect mTouchTargetForDismissButton;
- public BannerMessageView(Context context) {
+ public BannerMessageView(@NonNull Context context) {
super(context);
}
- public BannerMessageView(Context context,
- @Nullable AttributeSet attrs) {
+ public BannerMessageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
- public BannerMessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ public BannerMessageView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public BannerMessageView(Context context, AttributeSet attrs, int defStyleAttr,
+ public BannerMessageView(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt
new file mode 100644
index 000000000000..fbf910a42423
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.settingslib.widget
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.provider.DeviceConfig
+import android.transition.Fade
+import android.transition.Transition
+import android.transition.TransitionListenerAdapter
+import android.transition.TransitionManager
+import android.transition.TransitionSet
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.LinearInterpolator
+import android.widget.TextView
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.banner.R
+import java.time.Duration
+
+/** Callback to communicate when a banner message resolution animation is completed. */
+fun interface ResolutionCompletedCallback {
+ fun onCompleted()
+}
+
+internal class ResolutionAnimator(
+ private val data: Data,
+ private val preferenceViewHolder: PreferenceViewHolder,
+) {
+
+ data class Data(
+ val resolutionMessage: CharSequence,
+ val resolutionCompletedCallback: ResolutionCompletedCallback,
+ )
+
+ private val defaultBannerContent: View?
+ get() = preferenceViewHolder.findView(R.id.banner_content)
+ private val resolvedTextView: TextView?
+ get() = preferenceViewHolder.findView(R.id.resolved_banner_text)
+
+ fun startResolutionAnimation() {
+ resolvedTextView?.text = data.resolutionMessage
+ resolvedTextView?.resolutionDrawable?.reset()
+
+ val transitionSet =
+ TransitionSet()
+ .setOrdering(TransitionSet.ORDERING_SEQUENTIAL)
+ .setInterpolator(linearInterpolator)
+ .addTransition(hideIssueContentTransition)
+ .addTransition(
+ showResolvedContentTransition
+ .clone()
+ .addListener(
+ object : TransitionListenerAdapter() {
+ override fun onTransitionEnd(transition: Transition) {
+ super.onTransitionEnd(transition)
+ startIssueResolvedAnimation()
+ }
+ }
+ )
+ )
+
+ preferenceViewHolder.itemView.post {
+ TransitionManager.beginDelayedTransition(
+ preferenceViewHolder.itemView as ViewGroup,
+ transitionSet,
+ )
+
+ defaultBannerContent?.visibility = View.INVISIBLE
+ resolvedTextView?.visibility = View.VISIBLE
+ }
+
+ preferenceViewHolder.itemView.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {}
+
+ override fun onViewDetachedFromWindow(v: View) {
+ v.removeOnAttachStateChangeListener(this)
+ cancelAnimationsAndFinish()
+ }
+ }
+ )
+ }
+
+ private fun startIssueResolvedAnimation() {
+ val animatedDrawable = resolvedTextView?.resolutionDrawable
+
+ if (animatedDrawable == null) {
+ hideResolvedUiAndFinish()
+ return
+ }
+
+ animatedDrawable.apply {
+ clearAnimationCallbacks()
+ registerAnimationCallback(
+ object : Animatable2.AnimationCallback() {
+ override fun onAnimationEnd(drawable: Drawable) {
+ super.onAnimationEnd(drawable)
+ hideResolvedUiAndFinish()
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ private fun hideResolvedUiAndFinish() {
+ val hideTransition =
+ hideResolvedContentTransition
+ .clone()
+ .setInterpolator(linearInterpolator)
+ .addListener(
+ object : TransitionListenerAdapter() {
+ override fun onTransitionEnd(transition: Transition) {
+ super.onTransitionEnd(transition)
+ data.resolutionCompletedCallback.onCompleted()
+ }
+ }
+ )
+ TransitionManager.beginDelayedTransition(
+ preferenceViewHolder.itemView as ViewGroup,
+ hideTransition,
+ )
+ resolvedTextView?.visibility = View.GONE
+ }
+
+ private fun cancelAnimationsAndFinish() {
+ TransitionManager.endTransitions(preferenceViewHolder.itemView as ViewGroup)
+
+ resolvedTextView?.visibility = View.GONE
+
+ val animatedDrawable = resolvedTextView?.resolutionDrawable
+ animatedDrawable?.clearAnimationCallbacks()
+ animatedDrawable?.stop()
+
+ data.resolutionCompletedCallback.onCompleted()
+ }
+
+ private companion object {
+ private val linearInterpolator = LinearInterpolator()
+
+ private val HIDE_ISSUE_CONTENT_TRANSITION_DURATION = Duration.ofMillis(333)
+ private val hideIssueContentTransition =
+ Fade(Fade.OUT).setDuration(HIDE_ISSUE_CONTENT_TRANSITION_DURATION.toMillis())
+
+ private val SHOW_RESOLVED_CONTENT_TRANSITION_DELAY = Duration.ofMillis(133)
+ private val SHOW_RESOLVED_CONTENT_TRANSITION_DURATION = Duration.ofMillis(250)
+ private val showResolvedContentTransition =
+ Fade(Fade.IN)
+ .setStartDelay(SHOW_RESOLVED_CONTENT_TRANSITION_DELAY.toMillis())
+ .setDuration(SHOW_RESOLVED_CONTENT_TRANSITION_DURATION.toMillis())
+
+ private val hideResolvedContentTransitionDelay
+ get() =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Duration.ofMillis(
+ DeviceConfig.getLong(
+ "settings_ui",
+ "banner_message_pref_hide_resolved_content_delay_millis",
+ 400,
+ )
+ )
+ } else {
+ Duration.ofMillis(400)
+ }
+
+ private val HIDE_RESOLVED_UI_TRANSITION_DURATION = Duration.ofMillis(167)
+ private val hideResolvedContentTransition
+ get() =
+ Fade(Fade.OUT)
+ .setStartDelay(hideResolvedContentTransitionDelay.toMillis())
+ .setDuration(HIDE_RESOLVED_UI_TRANSITION_DURATION.toMillis())
+
+ inline fun <reified T : View> PreferenceViewHolder.findView(id: Int): T? =
+ findViewById(id) as? T
+
+ val TextView.resolutionDrawable: AnimatedVectorDrawable?
+ get() = compoundDrawables.find { it != null } as? AnimatedVectorDrawable
+ }
+}
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index a377f312ffbf..c8375a992d30 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -30,5 +30,6 @@ android_library {
apex_available: [
"//apex_available:platform",
"com.android.healthfitness",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml b/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml
index a0d10c383445..56b05159a30f 100644
--- a/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml
+++ b/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml
@@ -18,6 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.widget.preference.illustration">
- <uses-sdk android:minSdkVersion="28" />
+ <uses-sdk android:minSdkVersion="30" />
</manifest>
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/strings.xml b/packages/SettingsLib/IllustrationPreference/res/values/strings.xml
new file mode 100644
index 000000000000..3a8aaf8b5092
--- /dev/null
+++ b/packages/SettingsLib/IllustrationPreference/res/values/strings.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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for an accessibility action that starts an animation [CHAR LIMIT=30] -->
+ <string name="settingslib_action_label_resume">resume</string>
+ <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=30] -->
+ <string name="settingslib_action_label_pause">pause</string>
+ <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] -->
+ <string name="settingslib_state_animation_playing">Animation playing</string>
+ <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] -->
+ <string name="settingslib_state_animation_paused">Animation paused</string>
+</resources>
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index af40c647e805..e818a603c5b4 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -32,6 +32,8 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -73,6 +75,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
private boolean mLottieDynamicColor;
private CharSequence mContentDescription;
private boolean mIsTablet;
+ private boolean mIsAnimatable;
private boolean mIsAnimationPaused;
/**
@@ -81,6 +84,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
public interface OnBindListener {
/**
* Called when when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
+ *
* @param animationView the animation view for this preference.
*/
void onBind(LottieAnimationView animationView);
@@ -144,16 +148,6 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
(FrameLayout) holder.findViewById(R.id.middleground_layout);
final LottieAnimationView illustrationView =
(LottieAnimationView) holder.findViewById(R.id.lottie_view);
- // Pause and resume animation
- illustrationFrame.setOnClickListener(v -> {
- mIsAnimationPaused = !mIsAnimationPaused;
- if (mIsAnimationPaused) {
- illustrationView.pauseAnimation();
- } else {
- illustrationView.resumeAnimation();
- }
- });
-
if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
illustrationView.setContentDescription(mContentDescription);
illustrationView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -171,12 +165,13 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
illustrationView.setCacheComposition(mCacheComposition);
handleImageWithAnimation(illustrationView, illustrationFrame);
+ handleAnimationControl(illustrationView, illustrationFrame);
handleImageFrameMaxHeight(backgroundView, illustrationView);
if (mIsAutoScale) {
illustrationView.setScaleType(mIsAutoScale
- ? ImageView.ScaleType.CENTER_CROP
- : ImageView.ScaleType.CENTER_INSIDE);
+ ? ImageView.ScaleType.CENTER_CROP
+ : ImageView.ScaleType.CENTER_INSIDE);
}
handleMiddleGroundView(middleGroundLayout);
@@ -377,6 +372,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
final Drawable drawable = illustrationView.getDrawable();
if (drawable != null) {
startAnimation(drawable);
+ mIsAnimatable = false;
}
}
@@ -386,10 +382,12 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
final Drawable drawable = illustrationView.getDrawable();
if (drawable != null) {
startAnimation(drawable);
+ mIsAnimatable = false;
} else {
// The lottie image from the raw folder also returns null because the ImageView
// couldn't handle it now.
startLottieAnimationWith(illustrationView, mImageUri);
+ mIsAnimatable = true;
}
}
@@ -418,10 +416,12 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
final Drawable drawable = illustrationView.getDrawable();
if (drawable != null) {
startAnimation(drawable);
+ mIsAnimatable = false;
} else {
// The lottie image from the raw folder also returns null because the ImageView
// couldn't handle it now.
startLottieAnimationWith(illustrationView, mImageResId);
+ mIsAnimatable = true;
}
}
}
@@ -459,6 +459,60 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
((Animatable) drawable).start();
}
+ private void handleAnimationControl(LottieAnimationView illustrationView,
+ ViewGroup container) {
+ if (mIsAnimatable) {
+ // TODO(b/397340540): list out pages having illustration without a content description.
+ if (TextUtils.isEmpty(mContentDescription)) {
+ Log.w(TAG, "Illustration should have a content description. preference key = "
+ + getKey());
+ }
+ // Enable pause and resume abilities to animation only
+ container.setOnClickListener(v -> {
+ mIsAnimationPaused = !mIsAnimationPaused;
+ if (mIsAnimationPaused) {
+ illustrationView.pauseAnimation();
+ } else {
+ illustrationView.resumeAnimation();
+ }
+ updateAccessibilityAction(container);
+ });
+
+ updateAccessibilityAction(container);
+ }
+ }
+
+ private void updateAccessibilityAction(ViewGroup container) {
+ // Setting the state of animation
+ container.setStateDescription(getStateDescriptionForAnimation());
+ container.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ final AccessibilityAction clickAction = new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLICK,
+ getActionLabelForAnimation());
+ info.addAction(clickAction);
+ }
+ });
+ }
+
+ private String getActionLabelForAnimation() {
+ if (mIsAnimationPaused) {
+ return getContext().getString(R.string.settingslib_action_label_resume);
+ } else {
+ return getContext().getString(R.string.settingslib_action_label_pause);
+ }
+ }
+
+ private String getStateDescriptionForAnimation() {
+ if (mIsAnimationPaused) {
+ return getContext().getString(R.string.settingslib_state_animation_paused);
+ } else {
+ return getContext().getString(R.string.settingslib_state_animation_playing);
+ }
+ }
+
private static void startLottieAnimationWith(LottieAnimationView illustrationView,
Uri imageUri) {
final InputStream inputStream =
@@ -514,15 +568,20 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
mIsAutoScale = false;
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs,
- com.airbnb.lottie.R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
- mImageResId = a.getResourceId(com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_rawRes, 0);
+ com.airbnb.lottie.R.styleable.LottieAnimationView, /* defStyleAttr= */ 0,
+ /* defStyleRes= */ 0);
+ mImageResId = a.getResourceId(
+ com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_rawRes,
+ /* defValue= */ 0);
mCacheComposition = a.getBoolean(
- com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_cacheComposition, true);
+ com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_cacheComposition,
+ /* defValue= */ true);
a = context.obtainStyledAttributes(attrs,
- R.styleable.IllustrationPreference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
+ R.styleable.IllustrationPreference, /* defStyleAttr= */ 0,
+ /* defStyleRes= */ 0);
mLottieDynamicColor = a.getBoolean(R.styleable.IllustrationPreference_dynamicColor,
- false);
+ /* defValue= */ false);
a.recycle();
}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index dc5c9b297181..b6e80c784f10 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -18,7 +18,7 @@
<resources>
<style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
<item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
- <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainerLowest</item>
+ <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
<item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
index 6a0632073de9..a04fce7eeb86 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
@@ -47,7 +47,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
private val mHandler = Handler(Looper.getMainLooper())
- private val syncRunnable = Runnable { updatePreferences() }
+ private val syncRunnable = Runnable { updatePreferencesList() }
init {
val context = preferenceGroup.context
@@ -64,7 +64,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
true, /* resolveRefs */
)
mLegacyBackgroundRes = outValue.resourceId
- updatePreferences()
+ updatePreferencesList()
}
@SuppressLint("RestrictedApi")
@@ -82,7 +82,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
updateBackground(holder, position)
}
- private fun updatePreferences() {
+ private fun updatePreferencesList() {
val oldList = ArrayList(mRoundCornerMappingList)
mRoundCornerMappingList = ArrayList()
mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup)
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
index 1776d252e28b..1f4a48df55b0 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
@@ -50,10 +50,6 @@ object SettingsThemeHelper {
}
private fun tryInit(context: Context) {
- if (expressiveThemeState != ExpressiveThemeState.UNKNOWN) {
- return
- }
-
expressiveThemeState =
if (
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) &&
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index d94450b1cabd..a029f56cf1d7 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -26,14 +26,14 @@ flag {
name: "enable_le_audio_sharing"
namespace: "pixel_cross_device_control"
description: "Gates whether to enable LE audio sharing"
- bug: "323125723"
+ bug: "388674074"
}
flag {
name: "enable_le_audio_qr_code_private_broadcast_sharing"
namespace: "pixel_cross_device_control"
description: "Gates whether to enable LE audio private broadcast sharing via QR code"
- bug: "323125723"
+ bug: "388674074"
}
flag {
@@ -229,3 +229,23 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "adopt_primary_group_management_api_v2"
+ namespace: "cross_device_experiences"
+ description: "Adopt more Bluetooth LE broadcast primary group management APIs post launch"
+ bug: "381946931"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "promote_audio_sharing_for_second_auto_connected_lea_device"
+ namespace: "cross_device_experiences"
+ description: "Show audio sharing promote notification or dialog when the second lea device is auto connected"
+ bug: "395786392"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
index 6015be8c894f..aa5e9d28cabe 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
@@ -23,10 +23,10 @@
<clip-path
android:pathData="
M0,0
- V13.5,0
- H13.5,20
- V0,20
- H0,0
+ H16
+ V12
+ H0
+ Z
M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
<path
android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
index fb73b6b253e1..f7bf8518a55c 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
@@ -8,10 +8,10 @@
<clip-path
android:pathData="
M0,0
- V13.5,0
- H13.5,20
- V0,20
- H0,0
+ H16
+ V12
+ H0
+ Z
M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
<path
android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
index 7c4c1c6b1126..016c614846e9 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
@@ -23,10 +23,10 @@
<clip-path
android:pathData="
M0,0
- V13.5,0
- H13.5,20
- V0,20
- H0,0
+ H16
+ V12
+ H0
+ Z
M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
<path
android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
index d23680d17b9c..b8304151eefe 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
@@ -23,10 +23,10 @@
<clip-path
android:pathData="
M0,0
- V13.5,0
- H13.5,20
- V0,20
- H0,0
+ H16
+ V12
+ H0
+ Z
M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
<path
android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/core/res/res/values-w204dp-round-watch/dimens_watch.xml b/packages/SettingsLib/res/layout/activity_create_new_user.xml
index 3509474c5c2e..7453b53a6956 100644
--- a/core/res/res/values-w204dp-round-watch/dimens_watch.xml
+++ b/packages/SettingsLib/res/layout/activity_create_new_user.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?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");
@@ -14,8 +13,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<resources>
- <!-- watch's indeterminate progress bar dimens based on the current screen size -->
- <dimen name="loader_horizontal_min_width_watch">70dp</dimen>
- <dimen name="loader_horizontal_min_height_watch">15dp</dimen>
-</resources>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:orientation="vertical">
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index 3326b6034237..7ab096fae0db 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -116,4 +116,13 @@
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
<item name="android:textSize">16dp</item>
</style>
+
+ <style name="Theme.Transparent" parent="@android:style/Theme.DeviceDefault.Settings">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 572444edea29..bf86911ee683 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -30,13 +30,11 @@ import android.util.Log;
import androidx.annotation.ChecksSdkIntAtLeast;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.settingslib.flags.Flags;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -387,7 +385,7 @@ public class CsipDeviceManager {
preferredMainDevice.refresh();
hasChanged = true;
}
- syncAudioSharingStatusIfNeeded(preferredMainDevice);
+ syncAudioSharingSourceIfNeeded(preferredMainDevice);
}
if (hasChanged) {
log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -401,16 +399,13 @@ public class CsipDeviceManager {
return userManager != null && userManager.isManagedProfile();
}
- private void syncAudioSharingStatusIfNeeded(CachedBluetoothDevice mainDevice) {
+ private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingUIAvailable(mContext);
- if (isAudioSharingEnabled && mainDevice != null) {
+ if (isAudioSharingEnabled) {
if (isWorkProfile()) {
- log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, work profile");
+ log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
return;
}
- Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
- deviceSet.add(mainDevice);
- deviceSet.addAll(mainDevice.getMemberDevice());
boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager)
&& BluetoothUtils.hasConnectedBroadcastSource(
mainDevice, mBtManager);
@@ -424,6 +419,9 @@ public class CsipDeviceManager {
if (metadata != null && assistant != null) {
log("addMemberDevicesIntoMainDevice: sync audio sharing source after "
+ "combining the top level devices.");
+ Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
+ deviceSet.add(mainDevice);
+ deviceSet.addAll(mainDevice.getMemberDevice());
Set<BluetoothDevice> sinksToSync = deviceSet.stream()
.map(CachedBluetoothDevice::getDevice)
.filter(device ->
@@ -437,24 +435,8 @@ public class CsipDeviceManager {
}
}
}
- if (Flags.enableTemporaryBondDevicesUi()) {
- log("addMemberDevicesIntoMainDevice: sync temp bond metadata for audio sharing "
- + "sinks after combining the top level devices.");
- Set<BluetoothDevice> sinksToSync = deviceSet.stream()
- .map(CachedBluetoothDevice::getDevice).filter(Objects::nonNull).collect(
- Collectors.toSet());
- if (sinksToSync.stream().anyMatch(BluetoothUtils::isTemporaryBondDevice)) {
- for (BluetoothDevice device : sinksToSync) {
- if (!BluetoothUtils.isTemporaryBondDevice(device)) {
- log("addMemberDevicesIntoMainDevice: sync temp bond metadata for "
- + device.getAnonymizedAddress());
- BluetoothUtils.setTemporaryBondMetadata(device);
- }
- }
- }
- }
} else {
- log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, flag disabled");
+ log("addMemberDevicesIntoMainDevice: skip sync source, flag disabled");
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 84156429809b..367e38ed779d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -63,13 +63,14 @@ import com.google.common.collect.ImmutableList;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -84,6 +85,8 @@ import java.util.stream.Collectors;
public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
public static final String ACTION_LE_AUDIO_SHARING_STATE_CHANGE =
"com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
+ public static final String ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED =
+ "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_DEVICE_CONNECTED";
public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE";
@@ -105,6 +108,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
private static final String SYSUI_PKG = "com.android.systemui";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
+ private static final String VALID_PASSWORD_CHARACTERS =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,"
+ + ".<>?/";
+ private static final int PASSWORD_LENGTH = 16;
static final String NAME = "LE_AUDIO_BROADCAST";
private static final String UNDERLINE = "_";
@@ -1085,11 +1092,16 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
}
- private String generateRandomPassword() {
- String randomUUID = UUID.randomUUID().toString();
- // first 16 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
- return randomUUID.substring(0, 8) + randomUUID.substring(9, 13) + randomUUID.substring(14,
- 18);
+ private static String generateRandomPassword() {
+ SecureRandom random = new SecureRandom();
+ StringBuilder stringBuilder = new StringBuilder(PASSWORD_LENGTH);
+
+ for (int i = 0; i < PASSWORD_LENGTH; i++) {
+ int randomIndex = random.nextInt(VALID_PASSWORD_CHARACTERS.length());
+ stringBuilder.append(VALID_PASSWORD_CHARACTERS.charAt(randomIndex));
+ }
+
+ return stringBuilder.toString();
}
private void registerContentObserver() {
@@ -1189,6 +1201,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
@NonNull
private Map<Integer, List<BluetoothDevice>> getDeviceGroupsInBroadcast() {
+ if (mServiceBroadcastAssistant == null) return new HashMap<>();
boolean hysteresisModeFixEnabled =
BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext);
List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index d5cfe55813ee..460c790174ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -35,6 +35,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.SuppressLint;
import android.app.AutomaticZenRule;
import android.app.NotificationManager;
+import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
@@ -117,6 +118,14 @@ public class ZenMode implements Parcelable {
DISABLED_BY_OTHER
}
+ /**
+ * Information about the owner of a {@link ZenMode}. {@link #packageName()} is
+ * {@link SystemZenRules#PACKAGE_ANDROID} if the mode is system-owned; it may also be
+ * {@code null}, but only as an artifact of very old modes.
+ */
+ public record Owner(@Nullable String packageName, @Nullable ComponentName configurationActivity,
+ @Nullable ComponentName conditionProvider) { }
+
private final String mId;
private final AutomaticZenRule mRule;
private final Kind mKind;
@@ -198,7 +207,7 @@ public class ZenMode implements Parcelable {
}
@NonNull
- public AutomaticZenRule getRule() {
+ AutomaticZenRule getRule() {
return mRule;
}
@@ -207,6 +216,10 @@ public class ZenMode implements Parcelable {
return Strings.nullToEmpty(mRule.getName());
}
+ public void setName(@NonNull String name) {
+ mRule.setName(name);
+ }
+
@NonNull
public Kind getKind() {
return mKind;
@@ -217,6 +230,17 @@ public class ZenMode implements Parcelable {
return mStatus;
}
+ @NonNull
+ public Owner getOwner() {
+ return new Owner(mRule.getPackageName(), mRule.getConfigurationActivity(),
+ mRule.getOwner());
+ }
+
+ @Nullable
+ public String getOwnerPackage() {
+ return getOwner().packageName();
+ }
+
@AutomaticZenRule.Type
public int getType() {
return mRule.getType();
@@ -257,6 +281,26 @@ public class ZenMode implements Parcelable {
}
}
+ /**
+ * Returns the resource id of the icon for this mode. Note that this is the <em>stored</em>
+ * resource id, and thus can be different from the value in {@link #getIconKey()} -- in
+ * particular, for modes without a custom icon set, this method returns {@code 0} whereas
+ * {@link #getIconKey()} will return a default icon based on other mode properties.
+ *
+ * <p>Most callers are interested in {@link #getIconKey()}, unless they are editing the icon.
+ */
+ public int getIconResId() {
+ return mRule.getIconResId();
+ }
+
+ /**
+ * Sets the resource id of the icon for this mode.
+ * @see #getIconResId()
+ */
+ public void setIconResId(@DrawableRes int iconResId) {
+ mRule.setIconResId(iconResId);
+ }
+
/** Returns the interruption filter of the mode. */
@NotificationManager.InterruptionFilter
public int getInterruptionFilter() {
@@ -445,6 +489,10 @@ public class ZenMode implements Parcelable {
return mStatus == Status.ENABLED_AND_ACTIVE;
}
+ public boolean isManualInvocationAllowed() {
+ return mRule.isManualInvocationAllowed();
+ }
+
public boolean isSystemOwned() {
return SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName());
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java
index f5776989917e..b67339bace67 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java
@@ -70,8 +70,7 @@ public final class ZenModeDescriptions {
public String getTriggerDescriptionForAccessibility(@NonNull ZenMode mode) {
// Only one special case: time-based schedules, where we want to use full day names.
if (mode.isSystemOwned() && mode.getType() == TYPE_SCHEDULE_TIME) {
- ZenModeConfig.ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(
- mode.getRule().getConditionId());
+ ZenModeConfig.ScheduleInfo schedule = ZenModeSchedules.getTimeSchedule(mode);
if (schedule != null) {
String fullDaysSummary = SystemZenRules.getDaysOfWeekFull(mContext, schedule);
if (fullDaysSummary != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeSchedules.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeSchedules.java
new file mode 100644
index 000000000000..c981652f231d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeSchedules.java
@@ -0,0 +1,45 @@
+/*
+ * 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.settingslib.notification.modes;
+
+import android.service.notification.ZenModeConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class ZenModeSchedules {
+
+ /**
+ * Returns the {@link ZenModeConfig.ScheduleInfo} time schedule corresponding to the mode, or
+ * {@code null} if the mode is not time-schedule-based.
+ */
+ @Nullable
+ public static ZenModeConfig.ScheduleInfo getTimeSchedule(@NonNull ZenMode mode) {
+ return ZenModeConfig.tryParseScheduleConditionId(mode.getRule().getConditionId());
+ }
+
+ /**
+ * Returns the {@link ZenModeConfig.EventInfo} calendar schedule corresponding to the mode, or
+ * {@code null} if the mode is not calendar-schedule-based.
+ */
+ @Nullable
+ public static ZenModeConfig.EventInfo getCalendarSchedule(@NonNull ZenMode mode) {
+ return ZenModeConfig.tryParseEventConditionId(mode.getRule().getConditionId());
+ }
+
+ private ZenModeSchedules() { }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java
new file mode 100644
index 000000000000..c5e6f60e3fa6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java
@@ -0,0 +1,145 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.R;
+
+
+public class CreateUserActivity extends Activity {
+ private static final String TAG = "CreateUserActivity";
+
+ public static final String EXTRA_USER_NAME = "new_user_name";
+ public static final String EXTRA_IS_ADMIN = "is_admin";
+ public static final String EXTRA_USER_ICON_PATH = "user_icon_path";
+ private static final String DIALOG_STATE_KEY = "create_user_dialog_state";
+ private static final String EXTRA_CAN_CREATE_ADMIN = "can_create_admin";
+ private static final String EXTRA_FILE_AUTHORITY = "file_authority";
+
+ private CreateUserDialogController mCreateUserDialogController;
+ @VisibleForTesting
+ Dialog mSetupUserDialog;
+
+
+ /**
+ * Creates intent to start CreateUserActivity
+ */
+ public static @NonNull Intent createIntentForStart(@NonNull Context context,
+ boolean canCreateAdminUser, @NonNull String fileAuth) {
+ Intent intent = new Intent(context, CreateUserActivity.class);
+ intent.putExtra(EXTRA_CAN_CREATE_ADMIN, canCreateAdminUser);
+ intent.putExtra(EXTRA_FILE_AUTHORITY, fileAuth);
+ return intent;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+
+ mCreateUserDialogController = new CreateUserDialogController(
+ intent.getStringExtra(EXTRA_FILE_AUTHORITY));
+ setContentView(R.layout.activity_create_new_user);
+ if (savedInstanceState != null) {
+ mCreateUserDialogController.onRestoreInstanceState(savedInstanceState);
+ }
+ mSetupUserDialog = createDialog(intent.getBooleanExtra(EXTRA_CAN_CREATE_ADMIN, false));
+ mSetupUserDialog.show();
+ }
+
+ @Override
+ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ Bundle savedDialogState = savedInstanceState.getBundle(DIALOG_STATE_KEY);
+ if (savedDialogState != null && mSetupUserDialog != null) {
+ mSetupUserDialog.onRestoreInstanceState(savedDialogState);
+ }
+ }
+
+ private Dialog createDialog(boolean canCreateAdminUser) {
+ return mCreateUserDialogController.createDialog(
+ this,
+ this::startActivity,
+ canCreateAdminUser,
+ this::setSuccessResult,
+ this::cancel
+ );
+ }
+
+ @Override
+ public boolean onTouchEvent(@Nullable MotionEvent event) {
+ onBackInvoked();
+ return super.onTouchEvent(event);
+ }
+
+ private void onBackInvoked() {
+ if (mSetupUserDialog != null) {
+ mSetupUserDialog.dismiss();
+ }
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ @VisibleForTesting
+ void setSuccessResult(String userName, Drawable userIcon, String path, Boolean isAdmin) {
+ Intent intent = new Intent(this, CreateUserActivity.class);
+ intent.putExtra(EXTRA_USER_NAME, userName);
+ intent.putExtra(EXTRA_IS_ADMIN, isAdmin);
+ intent.putExtra(EXTRA_USER_ICON_PATH, path);
+
+ mSetupUserDialog.dismiss();
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @VisibleForTesting
+ void cancel() {
+ mSetupUserDialog.dismiss();
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ if (mSetupUserDialog != null && mSetupUserDialog.isShowing()) {
+ outState.putBundle(DIALOG_STATE_KEY, mSetupUserDialog.onSaveInstanceState());
+ }
+ mCreateUserDialogController.onSaveInstanceState(outState);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mCreateUserDialogController.onActivityResult(requestCode, resultCode, data);
+ }
+
+ private void startActivity(Intent intent, int requestCode) {
+ startActivityForResult(intent, requestCode);
+ mCreateUserDialogController.startingActivityForResult();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index d71b337228f6..d9f1b632323c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -242,7 +242,7 @@ public class CreateUserDialogController {
.setMessage(messageResId)
.setNegativeButtonText(R.string.cancel)
.setPositiveButtonText(R.string.next);
- mCustomDialogHelper.requestFocusOnTitle();
+ focus();
break;
case GRANT_ADMIN_DIALOG:
mEditUserInfoView.setVisibility(View.GONE);
@@ -255,7 +255,7 @@ public class CreateUserDialogController {
.setMessage(R.string.user_grant_admin_message)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.next);
- mCustomDialogHelper.requestFocusOnTitle();
+ focus();
if (mIsAdmin == null) {
mCustomDialogHelper.setButtonEnabled(false);
}
@@ -267,7 +267,7 @@ public class CreateUserDialogController {
.setTitle(R.string.user_info_settings_title)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.done);
- mCustomDialogHelper.requestFocusOnTitle();
+ focus();
mEditUserInfoView.setVisibility(View.VISIBLE);
mGrantAdminView.setVisibility(View.GONE);
break;
@@ -282,7 +282,7 @@ public class CreateUserDialogController {
mCustomDialogHelper.getDialog().dismiss();
break;
case EXIT_DIALOG:
- mCustomDialogHelper.getDialog().dismiss();
+ finish();
break;
default:
if (mCurrentState < EXIT_DIALOG) {
@@ -394,13 +394,21 @@ public class CreateUserDialogController {
return mCustomDialogHelper != null && mCustomDialogHelper.getDialog() != null;
}
+ void focus() {
+ mCustomDialogHelper.requestFocusOnTitle();
+ }
+
/**
* Runs callback and clears saved values after dialog is dismissed.
*/
public void finish() {
if (mCurrentState == CREATE_USER_AND_CLOSE) {
if (mSuccessCallback != null) {
- mSuccessCallback.onSuccess(mUserName, mNewUserIcon, Boolean.TRUE.equals(mIsAdmin));
+ if (mEditUserPhotoController != null && mCachedDrawablePath == null) {
+ mCachedDrawablePath = mEditUserPhotoController.getCachedDrawablePath();
+ }
+ mSuccessCallback.onSuccess(mUserName, mNewUserIcon, mCachedDrawablePath,
+ Boolean.TRUE.equals(mIsAdmin));
}
} else {
if (mCancelCallback != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java b/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java
index 3d18b59258b3..eed608e98cc9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java
@@ -18,6 +18,8 @@ package com.android.settingslib.users;
import android.graphics.drawable.Drawable;
+import androidx.annotation.Nullable;
+
/**
* Defines a callback when a new user data is filled out.
*/
@@ -27,8 +29,10 @@ public interface NewUserData {
* Consumes data relevant to new user that needs to be created.
* @param userName New user name.
* @param userImage New user icon.
+ * @param iconPath New user icon path.
* @param isNewUserAdmin A boolean that indicated whether new user has admin status.
*/
- void onSuccess(String userName, Drawable userImage, Boolean isNewUserAdmin);
+ void onSuccess(@Nullable String userName, @Nullable Drawable userImage,
+ @Nullable String iconPath, @Nullable Boolean isNewUserAdmin);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index 2eccaa626f3b..fd14d1ff6786 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -40,8 +40,6 @@ import android.content.Context;
import android.os.Looper;
import android.os.Parcel;
import android.os.UserManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.flags.Flags;
@@ -76,9 +74,6 @@ public class CsipDeviceManagerTest {
private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
- private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
- private static final String TEMP_BOND_METADATA =
- "<TEMP_BOND_TYPE>le_audio_sharing</TEMP_BOND_TYPE>";
private final static int GROUP1 = 1;
private final BluetoothClass DEVICE_CLASS_1 =
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
@@ -342,7 +337,6 @@ public class CsipDeviceManagerTest {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_returnTrue() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
@@ -352,6 +346,7 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -364,7 +359,6 @@ public class CsipDeviceManagerTest {
}
@Test
- @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
public void
addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_workProfile_doNothing() {
// Condition: The preferredDevice is main and there is another main device in top list
@@ -375,6 +369,7 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -382,8 +377,6 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
- when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
- .thenReturn(TEMP_BOND_METADATA.getBytes());
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
when(mUserManager.isManagedProfile()).thenReturn(true);
@@ -394,13 +387,10 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
verify(mAssistant, never()).addSource(mDevice1, metadata, /* isGroupOp= */ false);
- verify(mDevice1, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
- TEMP_BOND_METADATA.getBytes());
}
@Test
- @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
- public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncState() {
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncSource() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice1;
@@ -409,6 +399,7 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -416,8 +407,6 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
- when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
- .thenReturn(TEMP_BOND_METADATA.getBytes());
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -426,8 +415,6 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
verify(mAssistant).addSource(mDevice1, metadata, /* isGroupOp= */ false);
- verify(mDevice1).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
- TEMP_BOND_METADATA.getBytes());
}
@Test
@@ -449,13 +436,13 @@ public class CsipDeviceManagerTest {
}
@Test
- @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_returnTrue() {
// Condition: The preferredDevice is member and there are two main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice2;
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
mCachedDevice3.setGroupId(GROUP1);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(false);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
@@ -470,20 +457,16 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
verify(mAssistant, never()).addSource(any(BluetoothDevice.class),
any(BluetoothLeBroadcastMetadata.class), anyBoolean());
- verify(mDevice2, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
- TEMP_BOND_METADATA.getBytes());
- verify(mDevice3, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
- TEMP_BOND_METADATA.getBytes());
}
@Test
- @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
- public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncState() {
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncSource() {
// Condition: The preferredDevice is member and there are two main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice2;
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
mCachedDevice3.setGroupId(GROUP1);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -491,8 +474,6 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice1)).thenReturn(ImmutableList.of(state));
- when(mDevice1.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
- .thenReturn(TEMP_BOND_METADATA.getBytes());
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -507,10 +488,6 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
verify(mAssistant).addSource(mDevice2, metadata, /* isGroupOp= */ false);
verify(mAssistant).addSource(mDevice3, metadata, /* isGroupOp= */ false);
- verify(mDevice2).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
- TEMP_BOND_METADATA.getBytes());
- verify(mDevice3).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
- TEMP_BOND_METADATA.getBytes());
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index 6b30f159129e..71ecf5b76296 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -21,6 +21,7 @@ import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
import static android.app.AutomaticZenRule.TYPE_OTHER;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.AutomaticZenRule.TYPE_THEATER;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
@@ -35,6 +36,8 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import android.app.AutomaticZenRule;
+import android.content.ComponentName;
+import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import android.service.notification.Condition;
@@ -43,13 +46,17 @@ import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.internal.R;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@@ -72,6 +79,13 @@ public class ZenModeTest {
.setType(TYPE_OTHER)
.build();
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ }
+
@Test
public void testBasicMethods_mode() {
ZenMode zenMode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, true));
@@ -95,7 +109,7 @@ public class ZenModeTest {
assertThat(manualMode.canEditPolicy()).isTrue();
assertThat(manualMode.canBeDeleted()).isFalse();
assertThat(manualMode.isActive()).isFalse();
- assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID);
+ assertThat(manualMode.getOwnerPackage()).isEqualTo(PACKAGE_ANDROID);
}
@Test
@@ -153,7 +167,7 @@ public class ZenModeTest {
public void isCustomManual_scheduleTime_false() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Mode", Uri.parse("x"))
.setPackage(SystemZenRules.PACKAGE_ANDROID)
- .setType(AutomaticZenRule.TYPE_SCHEDULE_TIME)
+ .setType(TYPE_SCHEDULE_TIME)
.build();
ZenMode mode = new ZenMode("id", rule, zenConfigRuleFor(rule, false));
@@ -194,6 +208,23 @@ public class ZenModeTest {
}
@Test
+ public void getOwner_returnsOwnerDetails() {
+ AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
+ .setPackage("package")
+ .setConfigurationActivity(new ComponentName("package", "configActivity"))
+ .setOwner(new ComponentName("package", "conditionService"))
+ .build();
+ ZenMode zenMode = new ZenMode("id", azr, zenConfigRuleFor(azr, false));
+
+ ZenMode.Owner owner = zenMode.getOwner();
+ assertThat(owner.packageName()).isEqualTo("package");
+ assertThat(owner.configurationActivity()).isEqualTo(
+ new ComponentName("package", "configActivity"));
+ assertThat(owner.conditionProvider()).isEqualTo(
+ new ComponentName("package", "conditionService"));
+ }
+
+ @Test
public void getPolicy_interruptionFilterPriority_returnsZenPolicy() {
AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
@@ -245,7 +276,7 @@ public class ZenModeTest {
zenMode.setPolicy(ZEN_POLICY);
- assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo(
+ assertThat(zenMode.getInterruptionFilter()).isEqualTo(
INTERRUPTION_FILTER_PRIORITY);
assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY);
assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY);
@@ -397,6 +428,7 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isEqualTo("some.package");
assertThat(iconKey.resId()).isEqualTo(123);
+ assertThat(mode.getIconResId()).isEqualTo(123);
}
@Test
@@ -411,6 +443,7 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(123);
+ assertThat(mode.getIconResId()).isEqualTo(123);
}
@Test
@@ -425,15 +458,18 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isEqualTo("some.package");
assertThat(iconKey.resId()).isEqualTo(123);
+ assertThat(mode.getIconResId()).isEqualTo(123);
}
@Test
public void getIconKey_manualDnd_isDndIcon() {
- ZenIcon.Key iconKey = TestModeBuilder.MANUAL_DND.getIconKey();
+ ZenMode mode = TestModeBuilder.MANUAL_DND;
+ ZenIcon.Key iconKey = mode.getIconKey();
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
com.android.internal.R.drawable.ic_zen_mode_type_special_dnd);
+ assertThat(mode.getIconResId()).isEqualTo(0);
}
@Test
@@ -448,6 +484,7 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
com.android.internal.R.drawable.ic_zen_mode_type_bedtime);
+ assertThat(mode.getIconResId()).isEqualTo(0);
}
@Test
@@ -462,6 +499,7 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar);
+ assertThat(mode.getIconResId()).isEqualTo(0);
}
@Test
@@ -477,6 +515,77 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
com.android.internal.R.drawable.ic_zen_mode_type_special_dnd);
+ assertThat(mode.getIconResId()).isEqualTo(0);
+ }
+
+ @Test
+ public void setCustomModeConditionId_timeSchedule() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(PACKAGE_ANDROID)
+ .build();
+ ZenModeConfig.ScheduleInfo timeSchedule = new ZenModeConfig.ScheduleInfo();
+ timeSchedule.startHour = 9;
+ timeSchedule.endHour = 12;
+ timeSchedule.days = new int[] {Calendar.SATURDAY, Calendar.SUNDAY};
+ Uri scheduleUri = ZenModeConfig.toScheduleConditionId(timeSchedule);
+
+ mode.setCustomModeConditionId(mContext, scheduleUri);
+
+ assertThat(mode.getType()).isEqualTo(TYPE_SCHEDULE_TIME);
+ assertThat(ZenModeSchedules.getTimeSchedule(mode)).isEqualTo(timeSchedule);
+ assertThat(mode.getTriggerDescription()).isEqualTo("Sun, Sat, 9:00 AM - 12:00 PM");
+
+ assertThat(mode.getRule().getConditionId()).isEqualTo(scheduleUri);
+ assertThat(mode.getRule().getOwner()).isEqualTo(
+ ZenModeConfig.getScheduleConditionProvider());
+ }
+
+ @Test
+ public void setCustomModeConditionId_calendarSchedule() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(PACKAGE_ANDROID)
+ .build();
+ ZenModeConfig.EventInfo calendarSchedule = new ZenModeConfig.EventInfo();
+ calendarSchedule.calendarId = 1L;
+ calendarSchedule.calName = "My events";
+ Uri scheduleUri = ZenModeConfig.toEventConditionId(calendarSchedule);
+
+ mode.setCustomModeConditionId(mContext, scheduleUri);
+
+ assertThat(mode.getType()).isEqualTo(TYPE_SCHEDULE_CALENDAR);
+ assertThat(ZenModeSchedules.getCalendarSchedule(mode)).isEqualTo(calendarSchedule);
+ assertThat(mode.getTriggerDescription()).isEqualTo("My events");
+
+ assertThat(mode.getRule().getConditionId()).isEqualTo(scheduleUri);
+ assertThat(mode.getRule().getOwner()).isEqualTo(
+ ZenModeConfig.getEventConditionProvider());
+ }
+
+ @Test
+ public void setCustomModeConditionId_manualSchedule() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(PACKAGE_ANDROID)
+ .build();
+
+ mode.setCustomModeConditionId(mContext, ZenModeConfig.toCustomManualConditionId());
+
+ assertThat(mode.getType()).isEqualTo(TYPE_OTHER);
+ assertThat(mode.getTriggerDescription()).isEqualTo("");
+
+ assertThat(mode.getRule().getConditionId()).isEqualTo(
+ ZenModeConfig.toCustomManualConditionId());
+ assertThat(mode.getRule().getOwner()).isEqualTo(
+ ZenModeConfig.getCustomManualConditionProvider());
+ }
+
+ @Test
+ public void setCustomModeConditionId_nonSystemRule_throws() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage("some.other.package")
+ .build();
+
+ assertThrows(IllegalStateException.class,
+ () -> mode.setCustomModeConditionId(mContext, Uri.parse("blah")));
}
private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java
new file mode 100644
index 000000000000..f58eb7cc2e31
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.settingslib.users;
+
+import static com.android.settingslib.users.CreateUserActivity.EXTRA_IS_ADMIN;
+import static com.android.settingslib.users.CreateUserActivity.EXTRA_USER_ICON_PATH;
+import static com.android.settingslib.users.CreateUserActivity.EXTRA_USER_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class CreateUserActivityTest {
+
+ private static final String TEST_USER_NAME = "test_user";
+ private static final String TEST_USER_ICON_PATH = "/test_path";
+ private static final boolean TEST_IS_ADMIN = true;
+
+ private Context mContext;
+ private CreateUserActivity mCreateUserActivity;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mCreateUserActivity = Robolectric.buildActivity(CreateUserActivity.class).setup().get();
+ }
+
+ @Test
+ public void startActivity_startsActivityForResult() {
+ Intent activityIntent = CreateUserActivity.createIntentForStart(mContext, true, "");
+ mCreateUserActivity.startActivity(activityIntent, null);
+
+ assertThat(shadowOf(mCreateUserActivity).getNextStartedActivityForResult().intent)
+ .isEqualTo(activityIntent);
+ }
+
+ @Test
+ public void onTouchEvent_dismissesDialogAndCancelsResult() {
+ mCreateUserActivity.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0,
+ 0));
+
+ assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(shadowOf(mCreateUserActivity).getResultCode())
+ .isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void setSuccessResult_dismissesDialogAndSetsSuccessResult() {
+ Drawable mockDrawable = mock(Drawable.class);
+
+ mCreateUserActivity.setSuccessResult(TEST_USER_NAME, mockDrawable, TEST_USER_ICON_PATH,
+ TEST_IS_ADMIN);
+
+ assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(shadowOf(mCreateUserActivity).getResultCode()).isEqualTo(Activity.RESULT_OK);
+
+ Intent resultIntent = shadowOf(mCreateUserActivity).getResultIntent();
+ assertThat(resultIntent.getStringExtra(EXTRA_USER_NAME)).isEqualTo(TEST_USER_NAME);
+ assertThat(resultIntent.getBooleanExtra(EXTRA_IS_ADMIN, false)).isEqualTo(TEST_IS_ADMIN);
+ assertThat(resultIntent.getStringExtra(EXTRA_USER_ICON_PATH))
+ .isEqualTo(TEST_USER_ICON_PATH);
+ }
+
+ @Test
+ public void cancel_dismissesDialogAndSetsCancelResult() {
+ mCreateUserActivity.cancel();
+
+ assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(shadowOf(mCreateUserActivity).getResultCode())
+ .isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void onSaveInstanceState_savesDialogState() {
+ Bundle outState = new Bundle();
+ mCreateUserActivity.onSaveInstanceState(outState);
+
+ CreateUserActivity restoredActivity =
+ Robolectric.buildActivity(CreateUserActivity.class).setup(outState).get();
+
+ assertThat(restoredActivity.mSetupUserDialog).isNotNull();
+ assertThat(restoredActivity.mSetupUserDialog.isShowing()).isTrue();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
index 68312223b4b1..e60232339e4c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
@@ -211,7 +211,7 @@ public class CreateUserDialogControllerTest {
editText.setText(expectedNewName);
next.performClick();
verify(successCallback, times(1))
- .onSuccess(expectedNewName, null, true);
+ .onSuccess(expectedNewName, null, null, true);
verifyNoInteractions(cancelCallback);
}
@@ -233,7 +233,7 @@ public class CreateUserDialogControllerTest {
editText.setText(expectedNewName);
next.performClick();
verify(successCallback, times(1))
- .onSuccess(expectedNewName, null, false);
+ .onSuccess(expectedNewName, null, null, false);
verifyNoInteractions(cancelCallback);
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 8e3aa65fa5c7..bc281eea39d8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1503,7 +1503,7 @@ public class SettingsProvider extends ContentProvider {
if (DEBUG) {
Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value + ", "
+ ", " + tag + ", " + makeDefault + ", " + requestingUserId
- + ", " + forceNotify + ")");
+ + ", " + forceNotify + ", " + overrideableByRestore + ")");
}
return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId,
MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
@@ -1785,7 +1785,7 @@ public class SettingsProvider extends ContentProvider {
if (DEBUG) {
Slog.v(LOG_TAG, "insertSecureSetting(" + name + ", " + value + ", "
+ ", " + tag + ", " + makeDefault + ", " + requestingUserId
- + ", " + forceNotify + ")");
+ + ", " + forceNotify + ", " + overrideableByRestore + ")");
}
return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId,
MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
@@ -1946,7 +1946,7 @@ public class SettingsProvider extends ContentProvider {
boolean overrideableByRestore) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertSystemSetting(" + name + ", " + value + ", "
- + requestingUserId + ")");
+ + requestingUserId + ", " + overrideableByRestore + ")");
}
return mutateSystemSetting(name, value, /* tag= */ null, requestingUserId,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
index 17ebf6fc3235..0484defeb4d7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
@@ -30,6 +30,7 @@ import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.Slog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -94,6 +95,8 @@ final public class SettingsService extends Binder {
}
final static class MyShellCommand extends ShellCommand {
+ private static final String LOG_TAG = "SettingsShellCmd";
+
final SettingsProvider mProvider;
final boolean mDumping;
@@ -115,6 +118,7 @@ final public class SettingsService extends Binder {
String mTag = null;
int mResetMode = -1;
boolean mMakeDefault;
+ boolean mOverrideableByRestore;
MyShellCommand(SettingsProvider provider, boolean dumping) {
mProvider = provider;
@@ -209,6 +213,7 @@ final public class SettingsService extends Binder {
return -1;
}
break;
+ // At this point, mVerb == PUT
} else if (mKey == null) {
mKey = arg;
// keep going; there's another PUT arg
@@ -217,36 +222,8 @@ final public class SettingsService extends Binder {
// what we have so far is a valid command
valid = true;
// keep going; there may be another PUT arg
- } else if (mTag == null) {
- mTag = arg;
- if ("default".equalsIgnoreCase(mTag)) {
- mTag = null;
- mMakeDefault = true;
- if (peekNextArg() == null) {
- valid = true;
- } else {
- perr.println("Too many arguments");
- return -1;
- }
- break;
- }
- if (peekNextArg() == null) {
- valid = true;
- break;
- }
- } else { // PUT, final arg
- if (!"default".equalsIgnoreCase(arg)) {
- perr.println("Argument expected to be 'default'");
- return -1;
- }
- mMakeDefault = true;
- if (peekNextArg() == null) {
- valid = true;
- } else {
- perr.println("Too many arguments");
- return -1;
- }
- break;
+ } else {
+ valid = parseOptionalPutArgument(arg);
}
} while ((arg = getNextArg()) != null);
@@ -275,7 +252,8 @@ final public class SettingsService extends Binder {
pout.println(getForUser(iprovider, mUser, mTable, mKey));
break;
case PUT:
- putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault);
+ putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault,
+ mOverrideableByRestore);
break;
case DELETE:
pout.println("Deleted "
@@ -297,6 +275,41 @@ final public class SettingsService extends Binder {
return 0;
}
+ private boolean parseOptionalPutArgument(String arg) {
+ boolean valid = true;
+ // Given that the order is TAG default overrideableByRestore, we need to parse from the
+ // opposite direction
+ switch (arg) {
+ case "overrideableByRestore":
+ if (mOverrideableByRestore) {
+ valid = false;
+ } else {
+ mOverrideableByRestore = true;
+ }
+ break;
+ case "default":
+ if (mMakeDefault || mOverrideableByRestore) {
+ valid = false;
+ } else {
+ mMakeDefault = true;
+ }
+ break;
+ default: // tag
+ if (mMakeDefault || mOverrideableByRestore || mTag != null) {
+ valid = false;
+ } else {
+ mTag = arg;
+ }
+ break;
+ }
+ if (!valid) {
+ Slog.e(LOG_TAG, "parseOptionalPutArgument(" + arg + "): invalid state ("
+ + "mTag=" + mTag + ", mMakeDefault=" + mMakeDefault
+ + ", mOverrideableByRestore=" + mOverrideableByRestore + ")");
+ }
+ return valid;
+ }
+
List<String> listForUser(IContentProvider provider, int userHandle, String table) {
final String callListCommand;
if ("system".equals(table)) callListCommand = Settings.CALL_METHOD_LIST_SYSTEM;
@@ -351,7 +364,11 @@ final public class SettingsService extends Binder {
}
void putForUser(IContentProvider provider, int userHandle, final String table,
- final String key, final String value, String tag, boolean makeDefault) {
+ final String key, final String value, String tag, boolean makeDefault,
+ boolean overrideableByRestore) {
+ Slog.v(LOG_TAG, "putForUser(userId=" + userHandle + ", table=" + table + ", key=" + key
+ + ", value=" + value + ", tag=" + tag + ", makeDefault=" + makeDefault
+ + ", overrideableByRestore=" + overrideableByRestore + ")");
final String callPutCommand;
if ("system".equals(table)) {
callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
@@ -377,6 +394,9 @@ final public class SettingsService extends Binder {
if (makeDefault) {
arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
+ if (overrideableByRestore) {
+ arg.putBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true);
+ }
final AttributionSource attributionSource = new AttributionSource(
Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null);
provider.call(attributionSource, Settings.AUTHORITY,
@@ -474,10 +494,11 @@ final public class SettingsService extends Binder {
pw.println(" Print this help text.");
pw.println(" get [--user <USER_ID> | current] NAMESPACE KEY");
pw.println(" Retrieve the current value of KEY.");
- pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default]");
+ pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default] [overrideableByRestore]");
pw.println(" Change the contents of KEY to VALUE.");
- pw.println(" TAG to associate with the setting.");
+ pw.println(" TAG to associate with the setting (cannot be default or overrideableByRestore).");
pw.println(" {default} to set as the default, case-insensitive only for global/secure namespace");
+ pw.println(" {overrideableByRestore} to let the value be overridden by BackupManager on restore operations");
pw.println(" delete [--user <USER_ID> | current] NAMESPACE KEY");
pw.println(" Delete the entry for KEY.");
pw.println(" reset [--user <USER_ID> | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}");
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 85617bad1a91..70c042cb8eba 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -811,7 +811,7 @@ public class SettingsBackupTest {
Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
Settings.Secure.V_TO_U_RESTORE_DENYLIST,
Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
- Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY);
+ Settings.Secure.OTP_NOTIFICATION_REDACTION_LOCK_TIME);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 444389fb26ea..fdb07bdbe7f3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -155,7 +155,7 @@ constructor(
/** [ViewTransitionToken] to be used for storing transitioning view in [transitionRegistry] */
private val transitionToken =
if (Flags.decoupleViewControllerInAnimlib()) {
- ViewTransitionToken(transitioningView::class.java)
+ transitionRegistry?.register(transitioningView)
} else {
null
}
@@ -164,7 +164,7 @@ constructor(
private val ghostedView: View
get() =
if (Flags.decoupleViewControllerInAnimlib()) {
- transitionRegistry?.getView(transitionToken!!)
+ transitionToken?.let { token -> transitionRegistry?.getView(token) }
} else {
_ghostedView
}!!
@@ -186,10 +186,6 @@ constructor(
)
}
- if (Flags.decoupleViewControllerInAnimlib()) {
- transitionRegistry?.register(transitionToken!!, transitioningView)
- }
-
/** Find the first view with a background in [view] and its children. */
fun findBackground(view: View): Drawable? {
if (view.background != null) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt
index af3ca87bf788..280d90de7ace 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/IViewTransitionRegistry.kt
@@ -22,12 +22,12 @@ import android.view.View
interface IViewTransitionRegistry {
/**
- * Registers the transitioning [view] mapped to a [token]
+ * Registers the transitioning [view] mapped to returned token
*
- * @param token The token corresponding to the transitioning view
* @param view The view undergoing transition
+ * @return token mapped to the transitioning view
*/
- fun register(token: ViewTransitionToken, view: View)
+ fun register(view: View): ViewTransitionToken
/**
* Unregisters the transitioned view from its corresponding [token]
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt
index 86c7f76c6bee..882ff3b61ba9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewTransitionRegistry.kt
@@ -22,21 +22,21 @@ import java.lang.ref.WeakReference
/**
* A registry to temporarily store the view being transitioned into a Dialog (using
- * [DialogTransitionAnimator]) or an Activity (using [ActivityTransitionAnimator])
+ * [DialogTransitionAnimator]) or an Activity (using [ActivityTransitionAnimator]).
*/
class ViewTransitionRegistry : IViewTransitionRegistry {
/**
* A map of a unique token to a WeakReference of the View being transitioned. WeakReference
* ensures that Views are garbage collected whenever they become eligible and avoid any
- * memory leaks
+ * memory leaks.
*/
- private val registry by lazy { mutableMapOf<ViewTransitionToken, WeakReference<View>>() }
+ private val registry by lazy { mutableMapOf<ViewTransitionToken, ViewTransitionInfo>() }
/**
* A [View.OnAttachStateChangeListener] to be attached to all views stored in the registry to
* ensure that views (and their corresponding entry) is automatically removed when the view is
- * detached from the Window
+ * detached from the Window.
*/
private val listener by lazy {
object : View.OnAttachStateChangeListener {
@@ -45,74 +45,121 @@ class ViewTransitionRegistry : IViewTransitionRegistry {
}
override fun onViewDetachedFromWindow(view: View) {
- getViewToken(view)?.let { token -> unregister(token) }
+ // if view is detached from window, remove it from registry irrespective of number
+ // of reference held by clients/user of this registry
+ getViewToken(view)?.let { token -> remove(token) }
}
}
}
/**
- * Creates an entry of a unique "token" mapped to "transitioning view" in the registry
+ * Creates an entry of a unique token mapped to transitioning [view] in the registry.
*
- * @param token unique token associated with the transitioning view
* @param view view undergoing transitions
+ * @return unique token mapped to the view being registered
*/
- override fun register(token: ViewTransitionToken, view: View) {
+ override fun register(view: View): ViewTransitionToken {
+ // if view being registered is already present in the registry and has a unique token
+ // assigned to it, reuse that token
+ getViewToken(view)?.let { token ->
+ registry[token]?.let { info -> info.viewRefCount += 1 }
+ return token
+ }
+
// token embedded as a view tag enables to use a single listener for all views
+ val token = ViewTransitionToken(view::class.java)
view.setTag(R.id.tag_view_transition_token, token)
view.addOnAttachStateChangeListener(listener)
- registry[token] = WeakReference(view)
+ registry[token] = ViewTransitionInfo(WeakReference(view))
onRegistryUpdate()
+
+ return token
}
/**
- * Removes the entry associated with the unique "token" in the registry
+ * Unregisters a view mapped to the unique [token] in the registry. This will either remove the
+ * entry entirely from registry (if the reference count of the associated view reached zero) or
+ * will decrement the reference count of the associated view in the registry.
*
* @param token unique token associated with the transitioning view
*/
override fun unregister(token: ViewTransitionToken) {
- registry.remove(token)?.let {
- it.get()?.let { view ->
+ registry[token]?.let { info ->
+ info.viewRefCount -= 1
+ if (info.viewRefCount == 0) {
+ remove(token)
+ }
+ }
+ }
+
+ /**
+ * Removes the entry associated with the unique [token] in the registry.
+ *
+ * @param token unique token associated with the transitioning view
+ */
+ private fun remove(token: ViewTransitionToken) {
+ registry.remove(token)?.let { removedInfo ->
+ removedInfo.viewRef.get()?.let { view ->
view.removeOnAttachStateChangeListener(listener)
view.setTag(R.id.tag_view_transition_token, null)
}
- it.clear()
+ removedInfo.viewRef.clear()
onRegistryUpdate()
}
}
/**
- * Access a view from registry using unique "token" associated with it
+ * Access a view from registry using unique [token] associated with it.
* WARNING - this returns a StrongReference to the View stored in the registry
*/
override fun getView(token: ViewTransitionToken): View? {
- return registry[token]?.get()
+ return registry[token]?.viewRef?.get()
}
/**
- * Return token mapped to the [view], if it is present in the registry
+ * Return token mapped to the [view], if it is present in the registry.
*
* @param view the transitioning view whose token we are requesting
* @return token associated with the [view] if present, else null
*/
override fun getViewToken(view: View): ViewTransitionToken? {
- return (view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken)?.let { token ->
- getView(token)?.let { token }
+ // extract token from the view if it is embedded inside it as a tag
+ val token = view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken
+
+ // this should never really happen, but if token embedded inside the view as tag, doesn't
+ // point to a valid view in the registry, remove that token (tag) from the view and registry
+ if (token != null && getView(token) == null) {
+ view.setTag(R.id.tag_view_transition_token, null)
+ remove(token)
+ return null
}
+
+ return token
}
- /** Event call to run on registry update (on both [register] and [unregister]) */
+ /** Event call to run on registry update (on both [register] and [unregister]). */
override fun onRegistryUpdate() {
emitCountForTrace()
}
/**
* Utility function to emit number of non-null views in the registry whenever the registry is
- * updated (via [register] or [unregister])
+ * updated (via [register] or [unregister]).
*/
private fun emitCountForTrace() {
Trace.setCounter("transition_registry_view_count", registry.count().toLong())
}
+ /** Information associated with each transitioning view in the registry. */
+ private data class ViewTransitionInfo(
+
+ /** View being transitioned */
+ val viewRef: WeakReference<View>,
+
+ /** Count of clients (users of this registry) referencing same transitioning view */
+ var viewRefCount: Int = 1
+ )
+
companion object {
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ViewTransitionRegistry() }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
index 60eaa28e3822..b9aca25e1675 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -21,7 +21,6 @@ import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.observableTransitionState
import com.android.systemui.scene.shared.model.SceneDataSource
import kotlinx.coroutines.CoroutineScope
@@ -106,6 +105,6 @@ class SceneTransitionLayoutDataSource(
}
override fun freezeAndAnimateToCurrentState() {
- (state.transitionState as? TransitionState.Transition)?.freezeAndAnimateToCurrentState()
+ state.currentTransition?.freezeAndAnimateToCurrentState()
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 02de78bc84ce..db9035b1635b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -25,8 +25,8 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
@@ -43,10 +43,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
@@ -67,6 +65,7 @@ import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateElementFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
+import com.android.compose.theme.colorAttr
import com.android.settingslib.Utils
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -77,21 +76,18 @@ import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipBackground
-import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipHighlighted
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim
+import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingHorizontal
+import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingVertical
import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight
import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
-import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel
import com.android.systemui.statusbar.policy.Clock
-import kotlinx.coroutines.launch
object ShadeHeader {
object Elements {
@@ -110,6 +106,8 @@ object ShadeHeader {
object Dimensions {
val CollapsedHeight = 48.dp
val ExpandedHeight = 120.dp
+ val ChipPaddingHorizontal = 6.dp
+ val ChipPaddingVertical = 4.dp
}
object Colors {
@@ -118,12 +116,6 @@ object ShadeHeader {
val ColorScheme.onScrimDim: Color
get() = Color.DarkGray
-
- val ColorScheme.chipBackground: Color
- get() = Color.DarkGray
-
- val ColorScheme.chipHighlighted: Color
- get() = Color.LightGray
}
object TestTags {
@@ -149,9 +141,6 @@ fun ContentScope.CollapsedShadeHeader(
}
}
- val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
- val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle()
-
val isShadeLayoutWide = viewModel.isShadeLayoutWide
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
@@ -167,9 +156,9 @@ fun ContentScope.CollapsedShadeHeader(
) {
Clock(scale = 1f, onClick = viewModel::onClockClicked)
VariableDayDate(
- longerDateText = longerDateText,
- shorterDateText = shorterDateText,
- chipHighlight = viewModel.notificationsChipHighlight,
+ longerDateText = viewModel.longerDateText,
+ shorterDateText = viewModel.shorterDateText,
+ textColor = colorAttr(android.R.attr.textColorPrimary),
modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart),
)
}
@@ -229,8 +218,6 @@ fun ContentScope.ExpandedShadeHeader(
derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
}
- val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
- val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle()
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
@@ -269,9 +256,9 @@ fun ContentScope.ExpandedShadeHeader(
modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent),
) {
VariableDayDate(
- longerDateText = longerDateText,
- shorterDateText = shorterDateText,
- chipHighlight = viewModel.notificationsChipHighlight,
+ longerDateText = viewModel.longerDateText,
+ shorterDateText = viewModel.shorterDateText,
+ textColor = colorAttr(android.R.attr.textColorPrimary),
modifier = Modifier.widthIn(max = 90.dp),
)
Spacer(modifier = Modifier.weight(1f))
@@ -316,6 +303,7 @@ fun ContentScope.OverlayShadeHeader(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
+ val chipHighlight = viewModel.notificationsChipHighlight
if (isShadeLayoutWide) {
Clock(
scale = 1f,
@@ -324,28 +312,15 @@ fun ContentScope.OverlayShadeHeader(
)
Spacer(modifier = Modifier.width(5.dp))
}
- val chipHighlight = viewModel.notificationsChipHighlight
- NotificationIconChip(
- chipHighlight = chipHighlight,
+ NotificationsChip(
onClick = viewModel::onNotificationIconChipClicked,
+ backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme),
) {
- if (isShadeLayoutWide) {
- NotificationIcons(
- chipHighlight = chipHighlight,
- notificationIconContainerStatusBarViewBinder =
- viewModel.notificationIconContainerStatusBarViewBinder,
- modifier = Modifier.width(IntrinsicSize.Min).height(20.dp),
- )
- } else {
- val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
- val shorterDateText by
- viewModel.shorterDateText.collectAsStateWithLifecycle()
- VariableDayDate(
- longerDateText = longerDateText,
- shorterDateText = shorterDateText,
- chipHighlight = viewModel.notificationsChipHighlight,
- )
- }
+ VariableDayDate(
+ longerDateText = viewModel.longerDateText,
+ shorterDateText = viewModel.shorterDateText,
+ textColor = chipHighlight.foregroundColor(MaterialTheme.colorScheme),
+ )
}
}
},
@@ -357,14 +332,13 @@ fun ContentScope.OverlayShadeHeader(
) {
val chipHighlight = viewModel.quickSettingsChipHighlight
SystemIconChip(
- chipHighlight = chipHighlight,
+ backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme),
onClick = viewModel::onSystemIconChipClicked,
) {
StatusIcons(
viewModel = viewModel,
useExpandedFormat = false,
modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
- chipHighlight = chipHighlight,
)
BatteryIcon(
createBatteryMeterViewController =
@@ -534,6 +508,7 @@ private fun BatteryIcon(
batteryIcon.setPercentShowMode(
if (useExpandedFormat) BatteryMeterView.MODE_ESTIMATE else BatteryMeterView.MODE_ON
)
+ // TODO(b/397223606): Get the actual spec for this.
if (chipHighlight is HeaderChipHighlight.Strong) {
batteryIcon.updateColors(primaryColor, inverseColor, inverseColor)
} else if (chipHighlight is HeaderChipHighlight.Weak) {
@@ -546,11 +521,8 @@ private fun BatteryIcon(
@Composable
private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
- Row(modifier = modifier) {
- val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle()
-
- for (subId in subIds) {
- Spacer(modifier = Modifier.width(5.dp))
+ Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(5.dp)) {
+ for (subId in viewModel.mobileSubIds) {
AndroidView(
factory = { context ->
ModernShadeCarrierGroupMobileView.constructAndBind(
@@ -571,36 +543,10 @@ private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifie
}
@Composable
-private fun NotificationIcons(
- chipHighlight: HeaderChipHighlight,
- notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder,
- modifier: Modifier = Modifier,
-) {
- val scope = rememberCoroutineScope()
-
- AndroidView(
- factory = { context ->
- NotificationIconContainer(context, null).also { view ->
- view.setOverrideIconColor(true)
- scope.launch {
- notificationIconContainerStatusBarViewBinder.bindWhileAttached(
- view = view,
- displayId = context.displayId,
- )
- }
- }
- },
- update = { it.setUseInverseOverrideIconColor(chipHighlight is HeaderChipHighlight.Strong) },
- modifier = modifier,
- )
-}
-
-@Composable
private fun ContentScope.StatusIcons(
viewModel: ShadeHeaderViewModel,
useExpandedFormat: Boolean,
modifier: Modifier = Modifier,
- chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
) {
val localContext = LocalContext.current
val themedContext =
@@ -628,6 +574,8 @@ private fun ContentScope.StatusIcons(
viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS)
}
+ val chipHighlight = viewModel.quickSettingsChipHighlight
+
AndroidView(
factory = { context ->
iconManager.setTint(primaryColor, inverseColor)
@@ -664,6 +612,7 @@ private fun ContentScope.StatusIcons(
iconContainer.removeIgnoredSlot(locationSlot)
}
+ // TODO(b/397223606): Get the actual spec for this.
if (chipHighlight is HeaderChipHighlight.Strong) {
iconManager.setTint(inverseColor, primaryColor)
} else if (chipHighlight is HeaderChipHighlight.Weak) {
@@ -675,62 +624,49 @@ private fun ContentScope.StatusIcons(
}
@Composable
-private fun NotificationIconChip(
- chipHighlight: HeaderChipHighlight,
+private fun NotificationsChip(
onClick: () -> Unit,
modifier: Modifier = Modifier,
- content: @Composable RowScope.() -> Unit,
+ backgroundColor: Color = Color.Unspecified,
+ content: @Composable BoxScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
- Box(modifier = modifier) {
- Row(
- modifier =
- Modifier.align(Alignment.CenterStart)
- .clickable(
- interactionSource = interactionSource,
- indication = null,
- onClick = { onClick() },
- )
- .clip(RoundedCornerShape(25.dp))
- .background(
- if (chipHighlight is HeaderChipHighlight.Strong)
- MaterialTheme.colorScheme.chipHighlighted
- else MaterialTheme.colorScheme.chipBackground
- )
- .padding(horizontal = 8.dp, vertical = 4.dp)
- ) {
- content()
- }
+ Box(
+ modifier =
+ modifier
+ .clickable(
+ interactionSource = interactionSource,
+ indication = null,
+ onClick = onClick,
+ )
+ .background(backgroundColor, RoundedCornerShape(25.dp))
+ .padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical)
+ ) {
+ content()
}
}
@Composable
private fun SystemIconChip(
modifier: Modifier = Modifier,
- chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
+ backgroundColor: Color = Color.Unspecified,
onClick: (() -> Unit)? = null,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val isHovered by interactionSource.collectIsHoveredAsState()
val hoverModifier =
- Modifier.clip(RoundedCornerShape(CollapsedHeight / 4))
- .background(MaterialTheme.colorScheme.onScrimDim)
- val backgroundColor =
- if (chipHighlight is HeaderChipHighlight.Strong) MaterialTheme.colorScheme.chipHighlighted
- else MaterialTheme.colorScheme.chipBackground
+ with(MaterialTheme.colorScheme) {
+ Modifier.background(onScrimDim, RoundedCornerShape(CollapsedHeight / 4))
+ }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
modifier
- .thenIf(chipHighlight !is HeaderChipHighlight.None) {
- Modifier.graphicsLayer {
- shape = RoundedCornerShape(25.dp)
- clip = true
- }
- .background(backgroundColor)
- .padding(horizontal = 8.dp, vertical = 4.dp)
+ .thenIf(backgroundColor != Color.Unspecified) {
+ Modifier.background(backgroundColor, RoundedCornerShape(25.dp))
+ .padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical)
}
.thenIf(onClick != null) {
Modifier.clickable(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
index 64aada52626b..8fbd0519cbdf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
@@ -4,22 +4,16 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
-import com.android.compose.theme.colorAttr
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
@Composable
fun VariableDayDate(
longerDateText: String,
shorterDateText: String,
- chipHighlight: HeaderChipHighlight,
+ textColor: Color,
modifier: Modifier = Modifier,
) {
- val textColor =
- if (chipHighlight is HeaderChipHighlight.Strong)
- colorAttr(android.R.attr.textColorPrimaryInverse)
- else colorAttr(android.R.attr.textColorPrimary)
-
Layout(
contents =
listOf(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 8317aa39ef2b..54be4d81ea06 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -215,12 +215,7 @@ open class SimpleDigitalClockTextView(
)
}
- setInterpolatedViewBounds(
- getInterpolatedTextBounds(),
- widthMeasureSpec,
- heightMeasureSpec,
- force = true,
- )
+ setInterpolatedViewBounds(getInterpolatedTextBounds(), widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas) {
@@ -388,7 +383,6 @@ open class SimpleDigitalClockTextView(
interpBounds: Rect,
widthMeasureSpec: Int = measuredWidthAndState,
heightMeasureSpec: Int = measuredHeightAndState,
- force: Boolean = false,
) {
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
@@ -415,10 +409,7 @@ open class SimpleDigitalClockTextView(
)
}
- if (force || widthSpec != measuredWidthAndState || heightSpec != measuredHeightAndState) {
- setMeasuredDimension(widthSpec, heightSpec)
- parent?.requestLayout()
- }
+ setMeasuredDimension(widthSpec, heightSpec)
}
private fun updateXTranslation(inPoint: Point, interpolatedTextBounds: Rect): Point {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index c42e25b20e0d..d046ad114fa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -103,7 +103,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() {
fun testUpdateEmergencyButton() {
Mockito.`when`(telecomManager.isInCall).thenReturn(true)
Mockito.`when`(lockPatternUtils.isSecure(anyInt())).thenReturn(true)
- Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))
+ Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING))
.thenReturn(true)
underTest.updateEmergencyCallButton()
backgroundExecutor.runAllReady()
@@ -112,7 +112,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() {
/* isInCall= */ any(),
/* hasTelephonyRadio= */ any(),
/* simLocked= */ any(),
- /* isSecure= */ any()
+ /* isSecure= */ any(),
)
mainExecutor.runAllReady()
verify(emergencyButton)
@@ -120,7 +120,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() {
/* isInCall= */ eq(true),
/* hasTelephonyRadio= */ eq(true),
/* simLocked= */ any(),
- /* isSecure= */ eq(true)
+ /* isSecure= */ eq(true),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
index 052d520ac92f..18b68d2fa8a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
@@ -153,10 +153,12 @@ class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() {
private class FakeViewTransitionRegistry : IViewTransitionRegistry {
val registry = mutableMapOf<ViewTransitionToken, View>()
+ val token = ViewTransitionToken()
- override fun register(token: ViewTransitionToken, view: View) {
+ override fun register(view: View): ViewTransitionToken {
registry[token] = view
view.setTag(R.id.tag_view_transition_token, token)
+ return token
}
override fun unregister(token: ViewTransitionToken) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewTransitionRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewTransitionRegistryTest.kt
index ef91c793a2f3..b18eafd206ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewTransitionRegistryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewTransitionRegistryTest.kt
@@ -25,9 +25,9 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
import kotlin.test.Test
@SmallTest
@@ -36,24 +36,22 @@ class ViewTransitionRegistryTest : SysuiTestCase() {
private lateinit var view: View
private lateinit var underTest: ViewTransitionRegistry
- private var token: ViewTransitionToken = ViewTransitionToken()
@Before
fun setup() {
view = FrameLayout(mContext)
underTest = ViewTransitionRegistry()
- token = ViewTransitionToken()
}
@Test
fun testSuccessfulRegisterInViewTransitionRegistry() {
- underTest.register(token, view)
+ val token = underTest.register(view)
assertThat(underTest.getView(token)).isNotNull()
}
@Test
fun testSuccessfulUnregisterInViewTransitionRegistry() {
- underTest.register(token, view)
+ val token = underTest.register(view)
assertThat(underTest.getView(token)).isNotNull()
underTest.unregister(token)
@@ -62,13 +60,14 @@ class ViewTransitionRegistryTest : SysuiTestCase() {
@Test
fun testSuccessfulUnregisterOnViewDetachedFromWindow() {
- val view: View = mock {
- on { getTag(R.id.tag_view_transition_token) } doReturn token
- }
+ val view: View = mock()
- underTest.register(token, view)
+ val token = underTest.register(view)
+ assertThat(token).isEqualTo(token)
assertThat(underTest.getView(token)).isNotNull()
+ whenever(view.getTag(R.id.tag_view_transition_token)).thenReturn(token)
+
argumentCaptor<View.OnAttachStateChangeListener>()
.apply { verify(view).addOnAttachStateChangeListener(capture()) }
.firstValue
@@ -76,4 +75,58 @@ class ViewTransitionRegistryTest : SysuiTestCase() {
assertThat(underTest.getView(token)).isNull()
}
+
+ @Test
+ fun testMultipleRegisterOnSameView() {
+ val token = underTest.register(view)
+
+ // multiple register on same view should return same token
+ assertThat(underTest.register(view)).isEqualTo(token)
+
+ // 1st unregister doesn't remove the token from registry as refCount = 2
+ underTest.unregister(token)
+ assertThat(underTest.getView(token)).isNotNull()
+
+ // 2nd unregister removes the token from registry
+ underTest.unregister(token)
+ assertThat(underTest.getView(token)).isNull()
+ }
+
+ @Test
+ fun testMultipleRegisterOnSameViewRemovedAfterViewDetached() {
+ val view: View = mock()
+
+ val token = underTest.register(view)
+ whenever(view.getTag(R.id.tag_view_transition_token)).thenReturn(token)
+
+ assertThat(underTest.getViewToken(view)).isEqualTo(token)
+
+ // mock view's detach event
+ val caller = argumentCaptor<View.OnAttachStateChangeListener>()
+ .apply { verify(view).addOnAttachStateChangeListener(capture()) }
+ .firstValue
+
+ // register 3 times
+ underTest.register(view)
+ underTest.register(view)
+ underTest.register(view)
+
+ // unregister 1 time and verify entry should still be present in registry
+ underTest.unregister(token)
+ assertThat(underTest.getView(token)).isNotNull()
+
+ // view's associated entry should be gone from registry, after view detaches
+ caller.onViewDetachedFromWindow(view)
+ assertThat(underTest.getView(token)).isNull()
+ }
+
+ @Test
+ fun testDistinctViewsSameClassRegisterWithDifferentToken() {
+ var prev: ViewTransitionToken? = underTest.register(FrameLayout(mContext))
+ for (i in 0 until 10) {
+ val curr = underTest.register(FrameLayout(mContext))
+ assertThat(curr).isNotEqualTo(prev)
+ prev = curr
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 50762edc1179..88c9e74551fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -21,6 +21,7 @@ import android.content.res.Configuration
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
@@ -290,7 +291,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
verify(callback)
.onDismissed(
- eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
+ eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
eq<ByteArray?>(null), /* credentialAttestation */
eq(authContainer?.requestId ?: 0L),
)
@@ -310,7 +311,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
)
verify(callback)
.onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
eq<ByteArray?>(null), /* credentialAttestation */
eq(authContainer?.requestId ?: 0L),
)
@@ -325,7 +326,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
verify(callback)
.onDismissed(
- eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
+ eq(BiometricPrompt.DISMISSED_REASON_NEGATIVE),
eq<ByteArray?>(null), /* credentialAttestation */
eq(authContainer?.requestId ?: 0L),
)
@@ -352,7 +353,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
verify(callback)
.onDismissed(
- eq(AuthDialogCallback.DISMISSED_ERROR),
+ eq(BiometricPrompt.DISMISSED_REASON_ERROR),
eq<ByteArray?>(null), /* credentialAttestation */
eq(authContainer?.requestId ?: 0L),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index acc97a9f8642..a1a2aa70d869 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -138,9 +138,9 @@ public class AuthControllerTest extends SysuiTestCase {
@Mock
private IBiometricContextListener mContextListener;
@Mock
- private AuthDialog mDialog1;
+ private AuthContainerView mDialog1;
@Mock
- private AuthDialog mDialog2;
+ private AuthContainerView mDialog2;
@Mock
private CommandQueue mCommandQueue;
@Mock
@@ -382,7 +382,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
null, /* credentialAttestation */
mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
@@ -393,7 +393,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE,
null, /* credentialAttestation */
mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
@@ -404,7 +404,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED,
null, /* credentialAttestation */
mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
@@ -415,7 +415,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
null, /* credentialAttestation */
mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
@@ -426,7 +426,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonError_whenDismissedByError() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_ERROR,
null, /* credentialAttestation */
mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
@@ -437,7 +437,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED,
null, /* credentialAttestation */
mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
@@ -452,7 +452,7 @@ public class AuthControllerTest extends SysuiTestCase {
final byte[] credentialAttestation = generateRandomHAT();
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED,
credentialAttestation, mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
@@ -462,7 +462,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonContentViewMoreOptions_whenButtonPressed() throws Exception {
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS,
null, /* credentialAttestation */
mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
@@ -696,7 +696,7 @@ public class AuthControllerTest extends SysuiTestCase {
final byte[] credentialAttestation = generateRandomHAT();
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED,
credentialAttestation, mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
@@ -755,7 +755,7 @@ public class AuthControllerTest extends SysuiTestCase {
public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final long requestID = mAuthController.mCurrentDialog.getRequestId();
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
null, /* credentialAttestation */requestID);
mAuthController.onTryAgainPressed(requestID);
}
@@ -764,7 +764,7 @@ public class AuthControllerTest extends SysuiTestCase {
public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final long requestID = mAuthController.mCurrentDialog.getRequestId();
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
null /* credentialAttestation */, requestID);
mAuthController.onDeviceCredentialPressed(requestID);
}
@@ -818,7 +818,7 @@ public class AuthControllerTest extends SysuiTestCase {
// WHEN dialog is shown and then dismissed
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+ mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
null /* credentialAttestation */,
mAuthController.mCurrentDialog.getRequestId());
@@ -1218,14 +1218,14 @@ public class AuthControllerTest extends SysuiTestCase {
}
@Override
- protected AuthDialog buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo,
+ protected AuthContainerView buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo,
boolean requireConfirmation, int userId, int[] sensorIds,
String opPackageName, boolean skipIntro, long operationId, long requestId,
WakefulnessLifecycle wakefulnessLifecycle,
UserManager userManager,
LockPatternUtils lockPatternUtils, PromptViewModel viewModel) {
- AuthDialog dialog;
+ AuthContainerView dialog;
if (mBuildCount == 0) {
dialog = mDialog1;
} else if (mBuildCount == 1) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 98486a22854a..af6c65ec6d6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -538,7 +538,6 @@ object TestShortcuts {
simpleShortcutCategory(System, "System apps", "Open settings"),
simpleShortcutCategory(System, "System controls", "Lock screen"),
simpleShortcutCategory(System, "System controls", "View notifications"),
- simpleShortcutCategory(System, "System apps", "Take a note"),
simpleShortcutCategory(System, "System controls", "Take screenshot"),
simpleShortcutCategory(System, "System controls", "Go back"),
simpleShortcutCategory(MultiTasking, "Split screen", "Use full screen"),
@@ -570,7 +569,6 @@ object TestShortcuts {
simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES),
simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 208abf39611d..6c4325adced4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -35,12 +35,20 @@ package com.android.systemui.keyguard.domain.interactor
import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Available
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -54,6 +62,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -62,6 +72,8 @@ import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
@@ -416,4 +428,25 @@ class FromAodTransitionInteractorTest : SysuiTestCase() {
assertThat(transitionRepository)
.startedTransition(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
}
+
+ @Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun testTransitionToGlanceableHub_onWakeUpFromAod() =
+ kosmos.runTest {
+ val user = setCommunalV2Available(true)
+ fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1, user.id)
+ batteryRepository.fake.setDevicePluggedIn(true)
+
+ val currentScene by collectLastValue(communalSceneInteractor.currentScene)
+ fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
+
+ // Communal is not showing
+ Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ powerInteractor.setAwakeForTest()
+ testScope.advanceTimeBy(100) // account for debouncing
+
+ Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+ assertThat(transitionRepository).noTransitionsStarted()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt
new file mode 100644
index 000000000000..052dfd52887f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DozingToDreamingTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+
+ val underTest by lazy { kosmos.dozingToDreamingTransitionViewModel }
+
+ @Test
+ fun notificationShadeAlpha() =
+ kosmos.runTest {
+ val values by collectValues(underTest.notificationAlpha)
+ assertThat(values).isEmpty()
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+
+ assertThat(values).isNotEmpty()
+ values.forEach { assertThat(it).isEqualTo(0) }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
index 4e14fec8408f..943ada9346e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
@@ -18,6 +18,9 @@ package com.android.systemui.media.controls.ui.binder
import android.animation.Animator
import android.animation.ObjectAnimator
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
import android.testing.TestableLooper
import android.view.View
import android.widget.SeekBar
@@ -30,6 +33,7 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -61,11 +65,11 @@ class SeekBarObserverTest : SysuiTestCase() {
fun setUp() {
context.orCreateTestableResources.addOverride(
R.dimen.qs_media_enabled_seekbar_height,
- enabledHeight
+ enabledHeight,
)
context.orCreateTestableResources.addOverride(
R.dimen.qs_media_disabled_seekbar_height,
- disabledHeight
+ disabledHeight,
)
seekBarView = SeekBar(context)
@@ -110,14 +114,31 @@ class SeekBarObserverTest : SysuiTestCase() {
@Test
fun seekBarProgress() {
+ val elapsedTime = 3000
+ val duration = (1.5 * 60 * 60 * 1000).toInt()
// WHEN part of the track has been played
- val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
+ val data = SeekBarViewModel.Progress(true, true, true, false, elapsedTime, duration, true)
observer.onChanged(data)
// THEN seek bar shows the progress
- assertThat(seekBarView.progress).isEqualTo(3000)
- assertThat(seekBarView.max).isEqualTo(120000)
-
- val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
+ assertThat(seekBarView.progress).isEqualTo(elapsedTime)
+ assertThat(seekBarView.max).isEqualTo(duration)
+
+ val expectedProgress =
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(Measure(3, MeasureUnit.SECOND))
+ val expectedDuration =
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(
+ Measure(1, MeasureUnit.HOUR),
+ Measure(30, MeasureUnit.MINUTE),
+ Measure(0, MeasureUnit.SECOND),
+ )
+ val desc =
+ context.getString(
+ R.string.controls_media_seekbar_description,
+ expectedProgress,
+ expectedDuration,
+ )
assertThat(seekBarView.contentDescription).isEqualTo(desc)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index fee358a7c15d..83860ecf168b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -63,6 +63,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.Optional;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -100,7 +101,7 @@ public class RotationLockTileTest extends SysuiTestCase {
@Mock
private BatteryController mBatteryController;
@Mock
- DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
+ Optional<DeviceStateRotationLockSettingController> mDeviceStateRotationLockSettingController;
@Mock
RotationPolicyWrapper mRotationPolicyWrapper;
@Mock
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt
index 84fc93008f49..a3dd67f85150 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt
@@ -22,6 +22,9 @@ import android.provider.AlarmClock
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback
@@ -31,22 +34,32 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import java.util.Date
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatcher
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeHeaderClockInteractorTest : SysuiTestCase() {
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val activityStarter = kosmos.activityStarter
private val nextAlarmController = kosmos.nextAlarmController
- val underTest = kosmos.shadeHeaderClockInteractor
+ private val underTest = kosmos.shadeHeaderClockInteractor
@Test
fun launchClockActivity_default() =
@@ -55,7 +68,7 @@ class ShadeHeaderClockInteractorTest : SysuiTestCase() {
verify(activityStarter)
.postStartActivityDismissingKeyguard(
argThat(IntentMatcherAction(AlarmClock.ACTION_SHOW_ALARMS)),
- any()
+ any(),
)
}
@@ -71,6 +84,75 @@ class ShadeHeaderClockInteractorTest : SysuiTestCase() {
underTest.launchClockActivity()
verify(activityStarter).postStartActivityDismissingKeyguard(any())
}
+
+ @Test
+ fun onTimezoneOrLocaleChanged_localeAndTimezoneChanged_emitsForEach() =
+ testScope.runTest {
+ val timeZoneOrLocaleChanges by collectValues(underTest.onTimezoneOrLocaleChanged)
+
+ sendIntentActionBroadcast(Intent.ACTION_TIMEZONE_CHANGED)
+ sendIntentActionBroadcast(Intent.ACTION_LOCALE_CHANGED)
+ sendIntentActionBroadcast(Intent.ACTION_LOCALE_CHANGED)
+ sendIntentActionBroadcast(Intent.ACTION_TIMEZONE_CHANGED)
+
+ assertThat(timeZoneOrLocaleChanges).hasSize(4)
+ }
+
+ @Test
+ fun onTimezoneOrLocaleChanged_timeChanged_doesNotEmit() =
+ testScope.runTest {
+ val timeZoneOrLocaleChanges by collectValues(underTest.onTimezoneOrLocaleChanged)
+ assertThat(timeZoneOrLocaleChanges).hasSize(1)
+
+ sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED)
+ sendIntentActionBroadcast(Intent.ACTION_TIME_TICK)
+
+ // Expect only 1 event to have been emitted onStart, but no more.
+ assertThat(timeZoneOrLocaleChanges).hasSize(1)
+ }
+
+ @Test
+ fun currentTime_timeChanged() =
+ testScope.runTest {
+ val currentTime by collectLastValue(underTest.currentTime)
+
+ sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED)
+ val earlierTime = checkNotNull(currentTime)
+
+ advanceTimeBy(3.seconds)
+ runCurrent()
+
+ sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED)
+ val laterTime = checkNotNull(currentTime)
+
+ assertThat(differenceBetween(laterTime, earlierTime)).isEqualTo(3.seconds)
+ }
+
+ @Test
+ fun currentTime_timeTicked() =
+ testScope.runTest {
+ val currentTime by collectLastValue(underTest.currentTime)
+
+ sendIntentActionBroadcast(Intent.ACTION_TIME_TICK)
+ val earlierTime = checkNotNull(currentTime)
+
+ advanceTimeBy(7.seconds)
+ runCurrent()
+
+ sendIntentActionBroadcast(Intent.ACTION_TIME_TICK)
+ val laterTime = checkNotNull(currentTime)
+
+ assertThat(differenceBetween(laterTime, earlierTime)).isEqualTo(7.seconds)
+ }
+
+ private fun differenceBetween(date1: Date, date2: Date): Duration {
+ return (date1.time - date2.time).milliseconds
+ }
+
+ private fun TestScope.sendIntentActionBroadcast(intentAction: String) {
+ kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, Intent(intentAction))
+ runCurrent()
+ }
}
private class IntentMatcherAction(private val action: String) : ArgumentMatcher<Intent> {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 061e04ef29f7..37b4688f753d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -25,12 +25,15 @@ import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.enableSingleShade
import com.android.systemui.shade.domain.interactor.enableSplitShade
+import com.android.systemui.shade.domain.interactor.shadeMode
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argThat
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -43,6 +46,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableSceneContainer
@@ -64,14 +68,15 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun mobileSubIds_update() =
testScope.runTest {
- val mobileSubIds by collectLastValue(underTest.mobileSubIds)
mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ runCurrent()
- assertThat(mobileSubIds).isEqualTo(listOf(1))
+ assertThat(underTest.mobileSubIds).isEqualTo(listOf(1))
mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ runCurrent()
- assertThat(mobileSubIds).isEqualTo(listOf(1, 2))
+ assertThat(underTest.mobileSubIds).isEqualTo(listOf(1, 2))
}
@Test
@@ -116,13 +121,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun onSystemIconChipClicked_lockedOnQsShade_collapsesShadeToLockscreen() =
testScope.runTest {
- kosmos.enableDualShade()
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- setDeviceEntered(false)
- setScene(Scenes.Lockscreen)
- setOverlay(Overlays.QuickSettingsShade)
- assertThat(currentOverlays).isNotEmpty()
underTest.onSystemIconChipClicked()
runCurrent()
@@ -134,13 +135,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun onSystemIconChipClicked_lockedOnNotifShade_expandsQsShade() =
testScope.runTest {
- kosmos.enableDualShade()
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- setDeviceEntered(false)
- setScene(Scenes.Lockscreen)
- setOverlay(Overlays.NotificationsShade)
- assertThat(currentOverlays).isNotEmpty()
underTest.onSystemIconChipClicked()
runCurrent()
@@ -166,13 +163,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun onSystemIconChipClicked_unlockedOnQsShade_collapsesShadeToGone() =
testScope.runTest {
- kosmos.enableDualShade()
+ setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- setDeviceEntered(true)
- setScene(Scenes.Gone)
- setOverlay(Overlays.QuickSettingsShade)
- assertThat(currentOverlays).isNotEmpty()
underTest.onSystemIconChipClicked()
runCurrent()
@@ -184,13 +177,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun onSystemIconChipClicked_unlockedOnNotifShade_expandsQsShade() =
testScope.runTest {
- kosmos.enableDualShade()
+ setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- setDeviceEntered(true)
- setScene(Scenes.Gone)
- setOverlay(Overlays.NotificationsShade)
- assertThat(currentOverlays).isNotEmpty()
underTest.onSystemIconChipClicked()
runCurrent()
@@ -203,13 +192,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun onNotificationIconChipClicked_lockedOnNotifShade_collapsesShadeToLockscreen() =
testScope.runTest {
- kosmos.enableDualShade()
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- setDeviceEntered(false)
- setScene(Scenes.Lockscreen)
- setOverlay(Overlays.NotificationsShade)
- assertThat(currentOverlays).isNotEmpty()
underTest.onNotificationIconChipClicked()
runCurrent()
@@ -221,13 +206,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun onNotificationIconChipClicked_lockedOnQsShade_expandsNotifShade() =
testScope.runTest {
- kosmos.enableDualShade()
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- setDeviceEntered(false)
- setScene(Scenes.Lockscreen)
- setOverlay(Overlays.QuickSettingsShade)
- assertThat(currentOverlays).isNotEmpty()
underTest.onNotificationIconChipClicked()
runCurrent()
@@ -240,13 +221,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun onNotificationIconChipClicked_unlockedOnNotifShade_collapsesShadeToGone() =
testScope.runTest {
- kosmos.enableDualShade()
+ setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- setDeviceEntered(true)
- setScene(Scenes.Gone)
- setOverlay(Overlays.NotificationsShade)
- assertThat(currentOverlays).isNotEmpty()
underTest.onNotificationIconChipClicked()
runCurrent()
@@ -258,13 +235,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun onNotificationIconChipClicked_unlockedOnQsShade_expandsNotifShade() =
testScope.runTest {
- kosmos.enableDualShade()
+ setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade)
val currentScene by collectLastValue(sceneInteractor.currentScene)
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- setDeviceEntered(true)
- setScene(Scenes.Gone)
- setOverlay(Overlays.QuickSettingsShade)
- assertThat(currentOverlays).isNotEmpty()
underTest.onNotificationIconChipClicked()
runCurrent()
@@ -319,22 +292,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() =
testScope.runTest {
- kosmos.enableDualShade()
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
-
// Test the lockscreen scenario.
- setScene(Scenes.Lockscreen)
- setOverlay(Overlays.NotificationsShade)
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
// Test the unlocked scenario.
- setDeviceEntered(true)
- setScene(Scenes.Gone)
- setOverlay(Overlays.NotificationsShade)
- assertThat(currentScene).isEqualTo(Scenes.Gone)
- assertThat(currentOverlays).isNotEmpty()
+ setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade)
assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
}
@@ -342,22 +306,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() =
testScope.runTest {
- kosmos.enableDualShade()
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
-
// Test the lockscreen scenario.
- setScene(Scenes.Lockscreen)
- setOverlay(Overlays.QuickSettingsShade)
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
// Test the unlocked scenario.
- setDeviceEntered(true)
- setScene(Scenes.Gone)
- setOverlay(Overlays.QuickSettingsShade)
- assertThat(currentScene).isEqualTo(Scenes.Gone)
- assertThat(currentOverlays).isNotEmpty()
+ setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade)
assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
}
@@ -365,21 +320,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
@Test
fun highlightChips_noOverlaysInDualShade_bothNone() =
testScope.runTest {
- kosmos.enableDualShade()
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
-
// Test the lockscreen scenario.
- setScene(Scenes.Lockscreen)
- assertThat(currentOverlays).isEmpty()
+ setupDualShadeState(scene = Scenes.Lockscreen)
assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
// Test the unlocked scenario.
- setDeviceEntered(true)
- setScene(Scenes.Gone)
- assertThat(currentScene).isEqualTo(Scenes.Gone)
- assertThat(currentOverlays).isEmpty()
+ setupDualShadeState(scene = Scenes.Gone)
assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
}
@@ -401,21 +348,43 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
)
}
- private fun setScene(key: SceneKey) {
- sceneInteractor.changeScene(key, "test")
+ private fun TestScope.setupDualShadeState(scene: SceneKey, overlay: OverlayKey? = null) {
+ kosmos.enableDualShade()
+ val shadeMode by collectLastValue(kosmos.shadeMode)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ if (scene == Scenes.Gone) {
+ // Unlock the device, marking the device has been entered.
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ }
+ runCurrent()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
+
+ sceneInteractor.changeScene(scene, "test")
+ checkNotNull(currentOverlays).forEach { sceneInteractor.instantlyHideOverlay(it, "test") }
+ runCurrent()
+ overlay?.let { sceneInteractor.showOverlay(it, "test") }
sceneInteractor.setTransitionState(
- MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(scene, setOfNotNull(overlay))
+ )
)
- testScope.runCurrent()
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(scene)
+ if (overlay == null) {
+ assertThat(currentOverlays).isEmpty()
+ } else {
+ assertThat(currentOverlays).containsExactly(overlay)
+ }
}
- private fun setOverlay(key: OverlayKey) {
- val currentOverlays = sceneInteractor.currentOverlays.value + key
- sceneInteractor.showOverlay(key, "test")
+ private fun setScene(key: SceneKey) {
+ sceneInteractor.changeScene(key, "test")
sceneInteractor.setTransitionState(
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(sceneInteractor.currentScene.value, currentOverlays)
- )
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
)
testScope.runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
index c6801f1ad9d5..3d8da6140ff7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -23,8 +23,8 @@ import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_VIA_SYS
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -35,14 +35,12 @@ import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
-import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
-import android.view.accessibility.Flags;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -385,30 +383,7 @@ public class CommandQueueTest extends SysuiTestCase {
}
@Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- public void addQsTile_withA11yQsShortcutFlagOff() {
- ComponentName c = new ComponentName("testpkg", "testcls");
-
- mCommandQueue.addQsTile(c);
- waitForIdleSync();
-
- verify(mCallbacks).addQsTile(eq(c));
- }
-
- @Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- public void addQsTileToFrontOrEnd_withA11yQsShortcutFlagOff_doNothing() {
- ComponentName c = new ComponentName("testpkg", "testcls");
-
- mCommandQueue.addQsTileToFrontOrEnd(c, true);
- waitForIdleSync();
-
- verifyNoMoreInteractions(mCallbacks);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- public void addQsTile_withA11yQsShortcutFlagOn() {
+ public void addQsTile() {
ComponentName c = new ComponentName("testpkg", "testcls");
mCommandQueue.addQsTile(c);
@@ -418,8 +393,7 @@ public class CommandQueueTest extends SysuiTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- public void addQsTileAtTheEnd_withA11yQsShortcutFlagOn() {
+ public void addQsTileAtTheEnd() {
ComponentName c = new ComponentName("testpkg", "testcls");
mCommandQueue.addQsTileToFrontOrEnd(c, true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
index e38ea30daf0e..9fd189f35c32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
@@ -20,11 +20,14 @@ 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.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
@@ -33,22 +36,20 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class CallChipInteractorTest : SysuiTestCase() {
- val kosmos = Kosmos()
+ val kosmos = testKosmos().useUnconfinedTestDispatcher()
val repo = kosmos.ongoingCallRepository
val underTest = kosmos.callChipInteractor
@Test
- fun ongoingCallState_matchesRepo() =
- kosmos.testScope.runTest {
+ fun ongoingCallState_matchesState() =
+ kosmos.runTest {
val latest by collectLastValue(underTest.ongoingCallState)
- val inCall = inCallModel(startTimeMs = 1000)
- repo.setOngoingCallState(inCall)
- assertThat(latest).isEqualTo(inCall)
+ addOngoingCallState(key = "testKey")
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
- val noCall = OngoingCallModel.NoCall
- repo.setOngoingCallState(noCall)
- assertThat(latest).isEqualTo(noCall)
+ removeOngoingCallState(key = "testKey")
+ assertThat(latest).isEqualTo(OngoingCallModel.NoCall)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index c7b3175a636f..fda4ab005446 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -27,7 +27,9 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -35,17 +37,11 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
-import com.android.systemui.statusbar.core.StatusBarRootModernization
-import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
-import com.android.systemui.statusbar.notification.shared.CallType
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -60,10 +56,7 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class CallChipViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val notificationListRepository = kosmos.activeNotificationListRepository
- private val testScope = kosmos.testScope
- private val repo = kosmos.ongoingCallRepository
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val chipBackgroundView = mock<ChipBackgroundContainer>()
private val chipView =
@@ -82,53 +75,53 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
fun chip_noCall_isHidden() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.NoCall)
+ removeOngoingCallState("testKey")
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
}
@Test
fun chip_inCall_zeroStartTime_isShownAsIconOnly() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 0))
+ addOngoingCallState(startTimeMs = 0)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
@Test
fun chip_inCall_negativeStartTime_isShownAsIconOnly() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = -2))
+ addOngoingCallState(startTimeMs = -2)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
@Test
fun chip_inCall_positiveStartTime_isShownAsTimer() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 345))
+ addOngoingCallState(startTimeMs = 345)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
}
@Test
fun chip_inCall_startTimeConvertedToElapsedRealtime() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
kosmos.fakeSystemClock.setElapsedRealtime(400_000)
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
+ addOngoingCallState(startTimeMs = 1000)
// The OngoingCallModel start time is relative to currentTimeMillis, so this call
// started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be
@@ -141,13 +134,11 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_positiveStartTime_connectedDisplaysFlagOn_iconIsNotifIcon() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val notifKey = "testNotifKey"
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 1000, notificationIcon = null, notificationKey = notifKey)
- )
+ addOngoingCallState(startTimeMs = 1000, statusBarChipIconView = null, key = notifKey)
assertThat((latest as OngoingActivityChipModel.Active).icon)
.isInstanceOf(
@@ -163,16 +154,14 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon_withContentDescription() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val notifIcon = createStatusBarIconViewOrNull()
- repo.setOngoingCallState(
- inCallModel(
- startTimeMs = 0,
- notificationIcon = notifIcon,
- appName = "Fake app name",
- )
+ addOngoingCallState(
+ startTimeMs = 0,
+ statusBarChipIconView = notifIcon,
+ appName = "Fake app name",
)
assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -190,16 +179,13 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(
- inCallModel(
- startTimeMs = 0,
- notificationIcon = createStatusBarIconViewOrNull(),
- notificationKey = "notifKey",
- appName = "Fake app name",
- )
+ addOngoingCallState(
+ key = "notifKey",
+ statusBarChipIconView = createStatusBarIconViewOrNull(),
+ appName = "Fake app name",
)
assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -219,10 +205,10 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOff_iconIsPhone() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null))
+ addOngoingCallState(statusBarChipIconView = null)
assertThat((latest as OngoingActivityChipModel.Active).icon)
.isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
@@ -237,16 +223,13 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(
- inCallModel(
- startTimeMs = 1000,
- notificationIcon = null,
- notificationKey = "notifKey",
- appName = "Fake app name",
- )
+ addOngoingCallState(
+ key = "notifKey",
+ statusBarChipIconView = null,
+ appName = "Fake app name",
)
assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -265,10 +248,10 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
fun chip_positiveStartTime_colorsAreAccentThemed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null))
+ addOngoingCallState(startTimeMs = 1000, promotedContent = null)
assertThat((latest as OngoingActivityChipModel.Active).colors)
.isEqualTo(ColorsModel.AccentThemed)
@@ -276,10 +259,10 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
fun chip_zeroStartTime_colorsAreAccentThemed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null))
+ addOngoingCallState(startTimeMs = 0, promotedContent = null)
assertThat((latest as OngoingActivityChipModel.Active).colors)
.isEqualTo(ColorsModel.AccentThemed)
@@ -287,19 +270,19 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
fun chip_resetsCorrectly() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
kosmos.fakeSystemClock.setElapsedRealtime(400_000)
// Start a call
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
+ addOngoingCallState(key = "testKey", startTimeMs = 1000)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java)
assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs)
.isEqualTo(398_000)
// End the call
- repo.setOngoingCallState(OngoingCallModel.NoCall)
+ removeOngoingCallState(key = "testKey")
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
// Let 100_000ms elapse
@@ -307,31 +290,31 @@ class CallChipViewModelTest : SysuiTestCase() {
kosmos.fakeSystemClock.setElapsedRealtime(500_000)
// Start a new call, which started 1000ms ago
- repo.setOngoingCallState(inCallModel(startTimeMs = 102_000))
+ addOngoingCallState(key = "testKey", startTimeMs = 102_000)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java)
assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs)
.isEqualTo(499_000)
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_inCall_nullIntent_nullClickListener() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null))
+ addOngoingCallState(contentIntent = null)
assertThat((latest as OngoingActivityChipModel.Active).onClickListenerLegacy).isNull()
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = pendingIntent))
+ addOngoingCallState(startTimeMs = 1000, contentIntent = pendingIntent)
val clickListener = (latest as OngoingActivityChipModel.Active).onClickListenerLegacy
assertThat(clickListener).isNotNull()
@@ -343,13 +326,13 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
- repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = pendingIntent))
+ addOngoingCallState(startTimeMs = 0, contentIntent = pendingIntent)
val clickListener = (latest as OngoingActivityChipModel.Active).onClickListenerLegacy
assertThat(clickListener).isNotNull()
@@ -362,33 +345,25 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_inCall_nullIntent_noneClickBehavior() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- postOngoingCallNotification(
- repository = notificationListRepository,
- startTimeMs = 1000L,
- intent = null,
- )
+ addOngoingCallState(startTimeMs = 1000, contentIntent = null)
assertThat((latest as OngoingActivityChipModel.Active).clickBehavior)
.isInstanceOf(OngoingActivityChipModel.ClickBehavior.None::class.java)
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_inCall_positiveStartTime_validIntent_clickBehaviorLaunchesIntent() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
- postOngoingCallNotification(
- repository = notificationListRepository,
- startTimeMs = 1000L,
- intent = pendingIntent,
- )
+ addOngoingCallState(startTimeMs = 1000, contentIntent = pendingIntent)
val clickBehavior = (latest as OngoingActivityChipModel.Active).clickBehavior
assertThat(clickBehavior)
@@ -403,17 +378,13 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_inCall_zeroStartTime_validIntent_clickBehaviorLaunchesIntent() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
- postOngoingCallNotification(
- repository = notificationListRepository,
- startTimeMs = 0L,
- intent = pendingIntent,
- )
+ addOngoingCallState(startTimeMs = 0, contentIntent = pendingIntent)
val clickBehavior = (latest as OngoingActivityChipModel.Active).clickBehavior
assertThat(clickBehavior)
@@ -435,27 +406,6 @@ class CallChipViewModelTest : SysuiTestCase() {
mock<StatusBarIconView>()
}
- fun postOngoingCallNotification(
- repository: ActiveNotificationListRepository,
- startTimeMs: Long,
- intent: PendingIntent?,
- ) {
- repository.activeNotifications.value =
- ActiveNotificationsStore.Builder()
- .apply {
- addIndividualNotif(
- activeNotificationModel(
- key = "notif1",
- whenTime = startTimeMs,
- callType = CallType.Ongoing,
- statusBarChipIcon = null,
- contentIntent = intent,
- )
- )
- }
- .build()
- }
-
private val PROMOTED_CONTENT_WITH_COLOR =
PromotedNotificationContentModel.Builder("notif")
.apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 6cfad8540491..005af366a6c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
import android.content.DialogInterface
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -44,10 +42,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
-import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -282,7 +280,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_notProjecting_clickListenerShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -299,7 +297,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_projectingEntireScreen_clickListenerShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -317,7 +315,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_projectingSingleTask_clickListenerShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -339,7 +337,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_clickListenerHasCujLegacy() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -359,7 +357,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_recordingState_hasClickBehavior() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -370,7 +368,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_notProjecting_expandActionBehaviorShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -386,7 +384,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_projectingEntireScreen_expandActionBehaviorShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -401,7 +399,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_projectingSingleTask_expandActionBehaviorShowsDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index e708382799c2..d6b10a89726e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -49,10 +49,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
-import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -506,7 +506,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_noScreen_clickListenerShowsGenericShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -527,7 +527,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_entireScreen_clickListenerShowsScreenShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -548,7 +548,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_singleTask_clickListenerShowsScreenShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -573,7 +573,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun chip_clickListenerHasCuj() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -597,7 +597,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_noScreen_hasClickBehavior() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -609,7 +609,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_entireScreen_hasClickBehavior() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -621,7 +621,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_singleTask_hasClickBehavior() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -637,11 +637,8 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(
- FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP,
- StatusBarRootModernization.FLAG_NAME,
- StatusBarChipsModernization.FLAG_NAME,
- )
+ @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+ @EnableChipsModernization
fun chip_noScreen_clickBehaviorShowsGenericShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -657,7 +654,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_entireScreen_clickBehaviorShowsScreenShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -673,7 +670,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun chip_singleTask_clickBehaviorShowsScreenShareDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index fc3af11c30b3..39b19d3c4191 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -31,9 +29,9 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
-import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import kotlin.test.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
@@ -64,7 +62,7 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() {
mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
@Test
- @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+ @DisableChipsModernization
fun createDialogLaunchOnClickListener_showsDialogOnClick() {
val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test")
val clickListener =
@@ -82,7 +80,7 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun createDialogLaunchOnClickCallback_showsDialogOnClick() {
val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test")
val clickCallback =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index ab475c5edb76..2f6bedb42e45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -37,6 +37,8 @@ import com.android.systemui.statusbar.layout.LetterboxAppearance
import com.android.systemui.statusbar.layout.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.layout.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
+import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -387,6 +389,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @DisableChipsModernization
fun statusBarMode_ongoingCallAndFullscreen_semiTransparent() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
@@ -398,6 +401,19 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @EnableChipsModernization
+ fun statusBarMode_ongoingProcessRequiresStatusBarVisible_andFullscreen_semiTransparent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ underTest.setOngoingProcessRequiresStatusBarVisible(true)
+ onSystemBarAttributesChanged(requestedVisibleTypes = WindowInsets.Type.navigationBars())
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+ }
+
+ @Test
+ @DisableChipsModernization
fun statusBarMode_ongoingCallButNotFullscreen_matchesAppearance() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
@@ -413,6 +429,23 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @EnableChipsModernization
+ fun statusBarMode_ongoingProcessRequiresStatusBarVisible_butNotFullscreen_matchesAppearance() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ underTest.setOngoingProcessRequiresStatusBarVisible(true)
+
+ onSystemBarAttributesChanged(
+ requestedVisibleTypes = WindowInsets.Type.statusBars(),
+ appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+ }
+
+ @Test
+ @DisableChipsModernization
fun statusBarMode_fullscreenButNotOngoingCall_matchesAppearance() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
@@ -427,6 +460,21 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @EnableChipsModernization
+ fun statusBarMode_fullscreen_butNotOngoingProcessRequiresStatusBarVisible_matchesAppearance() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ underTest.setOngoingProcessRequiresStatusBarVisible(false)
+ onSystemBarAttributesChanged(
+ requestedVisibleTypes = WindowInsets.Type.navigationBars(),
+ appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+ }
+
+ @Test
fun statusBarMode_transientShown_semiTransparent() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
index 426af264da07..83e26c4220b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection
+import android.app.Notification
+import android.graphics.Color
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.TestableLooper.RunWithLooper
@@ -72,4 +74,35 @@ class BundleEntryTest : SysuiTestCase() {
fun getKey_adapter() {
assertThat(underTest.entryAdapter.key).isEqualTo("key")
}
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isClearable_adapter() {
+ assertThat(underTest.entryAdapter.isClearable).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getSummarization_adapter() {
+ assertThat(underTest.entryAdapter.summarization).isNull()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getContrastedColor_adapter() {
+ assertThat(underTest.entryAdapter.getContrastedColor(context, false, Color.WHITE))
+ .isEqualTo(Notification.COLOR_DEFAULT)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun canPeek_adapter() {
+ assertThat(underTest.entryAdapter.canPeek()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getWhen_adapter() {
+ assertThat(underTest.entryAdapter.`when`).isEqualTo(0)
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 19d1224a9bf3..1f5c6722f38e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -163,9 +163,10 @@ public class NotificationEntryTest extends SysuiTestCase {
@Test
public void testIsExemptFromDndVisualSuppression_media() {
+ MediaSession session = new MediaSession(mContext, "test");
Notification.Builder n = new Notification.Builder(mContext, "")
.setStyle(new Notification.MediaStyle()
- .setMediaSession(mock(MediaSession.Token.class)))
+ .setMediaSession(session.getSessionToken()))
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
@@ -593,6 +594,76 @@ public class NotificationEntryTest extends SysuiTestCase {
assertThat(entry.getEntryAdapter().getGroupRoot()).isEqualTo(parent.getEntryAdapter());
}
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void isClearable_adapter() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ entry.setRow(row);
+
+ assertThat(entry.getEntryAdapter().isClearable()).isEqualTo(entry.isClearable());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getSummarization_adapter() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ Ranking ranking = new RankingBuilder(entry.getRanking())
+ .setSummarization("hello")
+ .build();
+ entry.setRanking(ranking);
+ entry.setRow(row);
+
+ assertThat(entry.getEntryAdapter().getSummarization()).isEqualTo("hello");
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getIcons_adapter() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ entry.setRow(row);
+
+ assertThat(entry.getEntryAdapter().getIcons()).isEqualTo(entry.getIcons());
+ }
+
private Notification.Action createContextualAction(String title) {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 7781df1ad91f..43cb9575b609 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
+import android.app.Notification.MediaStyle
+import android.media.session.MediaSession
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
+import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.dumpManager
@@ -36,6 +39,7 @@ import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -217,6 +221,16 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
mock<ExpandableNotificationRow>().apply {
whenever(isMediaRow).thenReturn(true)
}
+ sbn = SbnBuilder().setNotification(
+ Notification.Builder(context, "channel").setStyle(
+ MediaStyle().setMediaSession(
+ MediaSession(
+ context,
+ "tag"
+ ).sessionToken
+ )
+ ).build()
+ ).build()
}
collectionListener.onEntryAdded(fakeEntry)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index 4c1f4f17e00c..1b8d64d5483c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -80,9 +80,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.testKosmos
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.wmshell.BubblesManager
-import java.util.Optional
-import kotlin.test.assertNotNull
-import kotlin.test.fail
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
@@ -110,6 +107,9 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+import java.util.Optional
+import kotlin.test.assertNotNull
+import kotlin.test.fail
/** Tests for [NotificationGutsManager]. */
@SmallTest
@@ -509,7 +509,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
- whenever(row.isNonblockable).thenReturn(false)
whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -546,7 +545,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- whenever(row.isNonblockable).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
@@ -586,7 +584,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- whenever(row.isNonblockable).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
@@ -641,7 +638,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
): NotificationMenuRowPlugin.MenuItem {
val menuRow: NotificationMenuRowPlugin =
NotificationMenuRow(mContext, peopleNotificationIdentifier)
- menuRow.createMenu(row, row.entry.sbn)
+ menuRow.createMenu(row)
val menuItem = menuRow.getLongpressMenuItem(mContext)
assertNotNull(menuItem)
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 027e899e20df..9fdfca14a5b2 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
@@ -72,7 +72,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
public void testAttachDetach() {
NotificationMenuRowPlugin row =
new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewUtils.attachView(row.getMenuView());
TestableLooper.get(this).processAllMessages();
ViewUtils.detachView(row.getMenuView());
@@ -83,9 +83,9 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
public void testRecreateMenu() {
NotificationMenuRowPlugin row =
new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
assertTrue(row.getMenuView() != null);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
assertTrue(row.getMenuView() != null);
}
@@ -103,7 +103,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewGroup container = (ViewGroup) row.getMenuView();
// noti blocking
@@ -116,7 +116,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewGroup container = (ViewGroup) row.getMenuView();
// just for noti blocking
@@ -129,7 +129,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewGroup container = (ViewGroup) row.getMenuView();
// one for snooze and one for noti blocking
@@ -142,7 +142,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 1);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewGroup container = (ViewGroup) row.getMenuView();
// Clear menu
@@ -417,7 +417,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
public void testOnTouchMove() {
NotificationMenuRow row = Mockito.spy(
new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
doReturn(50f).when(row).getDismissThreshold();
doReturn(true).when(row).canBeDismissed();
doReturn(mView).when(row).getMenuView();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
index 9a6a6997b96f..081f52c4ff3b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
@@ -135,6 +135,7 @@ class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() {
.thenReturn(mock())
whenever(requireViewById<View>(R.id.app_name_text)).thenReturn(mock())
whenever(requireViewById<View>(R.id.conversation_text)).thenReturn(mock())
+ whenever(requireViewById<View>(R.id.title)).thenReturn(mock())
}
return mockView
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
index e5cb0fbc9e4b..885e71e7a7fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.testKosmos
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,7 +50,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
private val sectionsManager = mock<NotificationSectionsManager>()
private val msdlPlayer = kosmos.fakeMSDLPlayer
private var canRowBeDismissed = true
- private var magneticAnimationsCancelled = false
+ private var magneticAnimationsCancelled = MutableList(childrenNumber) { false }
private val underTest = kosmos.magneticNotificationRowManagerImpl
@@ -64,8 +65,10 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
NotificationTestHelper(mContext, mDependency, kosmos.testableLooper, featureFlags)
children = notificationTestHelper.createGroup(childrenNumber).childrenContainer
swipedRow = children.attachedChildren[childrenNumber / 2]
- configureMagneticRowListener(swipedRow)
- magneticAnimationsCancelled = false
+ children.attachedChildren.forEachIndexed { index, row ->
+ row.magneticRowListener = row.magneticRowListener.asTestableListener(index)
+ }
+ magneticAnimationsCancelled.replaceAll { false }
}
@Test
@@ -259,14 +262,14 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
underTest.onMagneticInteractionEnd(swipedRow, velocity = null)
// THEN magnetic animations are cancelled
- assertThat(magneticAnimationsCancelled).isTrue()
+ assertThat(magneticAnimationsCancelled[childrenNumber / 2]).isTrue()
}
@Test
fun onMagneticInteractionEnd_forMagneticNeighbor_cancelsMagneticAnimations() =
kosmos.testScope.runTest {
- val neighborRow = children.attachedChildren[childrenNumber / 2 - 1]
- configureMagneticRowListener(neighborRow)
+ val neighborIndex = childrenNumber / 2 - 1
+ val neighborRow = children.attachedChildren[neighborIndex]
// GIVEN that targets are set
setTargets()
@@ -275,9 +278,15 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
underTest.onMagneticInteractionEnd(neighborRow, null)
// THEN magnetic animations are cancelled
- assertThat(magneticAnimationsCancelled).isTrue()
+ assertThat(magneticAnimationsCancelled[neighborIndex]).isTrue()
}
+ @After
+ fun tearDown() {
+ // We reset the manager so that all MagneticRowListener can cancel all animations
+ underTest.reset()
+ }
+
private fun setDetachedState() {
val threshold = 100f
underTest.setSwipeThresholdPx(threshold)
@@ -302,27 +311,33 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
originalTranslation *
MagneticNotificationRowManagerImpl.MAGNETIC_REDUCTION
- private fun configureMagneticRowListener(row: ExpandableNotificationRow) {
- val listener =
- object : MagneticRowListener {
- override fun setMagneticTranslation(translation: Float) {
- row.translation = translation
- }
+ private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener {
+ val delegate = this
+ return object : MagneticRowListener {
+ override fun setMagneticTranslation(translation: Float) {
+ delegate.setMagneticTranslation(translation)
+ }
- override fun triggerMagneticForce(
- endTranslation: Float,
- springForce: SpringForce,
- startVelocity: Float,
- ) {}
+ override fun triggerMagneticForce(
+ endTranslation: Float,
+ springForce: SpringForce,
+ startVelocity: Float,
+ ) {
+ delegate.triggerMagneticForce(endTranslation, springForce, startVelocity)
+ }
- override fun cancelMagneticAnimations() {
- magneticAnimationsCancelled = true
- }
+ override fun cancelMagneticAnimations() {
+ magneticAnimationsCancelled[rowIndex] = true
+ delegate.cancelMagneticAnimations()
+ }
- override fun cancelTranslationAnimations() {}
+ override fun cancelTranslationAnimations() {
+ delegate.cancelTranslationAnimations()
+ }
- override fun canRowBeDismissed(): Boolean = canRowBeDismissed
+ override fun canRowBeDismissed(): Boolean {
+ return canRowBeDismissed
}
- row.magneticRowListener = listener
+ }
}
}
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 01ba4df3a314..7603eecd27ff 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
@@ -146,6 +146,20 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ fun resetViewStates_defaultHun_hasShadow() {
+ val headsUpTop = 200f
+ ambientState.headsUpTop = headsUpTop
+
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ assertThat(notificationRow.viewState.zTranslation).isGreaterThan(baseZ)
+ }
+
+ @Test
@DisableSceneContainer
fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 21297e3e64b7..c630a1c1e006 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -17,14 +17,15 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -54,11 +55,14 @@ import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.enableSingleShade
@@ -125,7 +129,6 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
kosmos.sharedNotificationContainerInteractor
}
private val largeScreenHeaderHelper by lazy { kosmos.mockLargeScreenHeaderHelper }
- private val communalSceneRepository by lazy { kosmos.communalSceneRepository }
lateinit var underTest: SharedNotificationContainerViewModel
@@ -591,6 +594,25 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
}
@Test
+ @DisableSceneContainer
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun isOnLockscreenFalseWhenCommunalShowing() =
+ kosmos.runTest {
+ val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
+
+ setTransition(
+ sceneTransition = Idle(Scenes.Bouncer),
+ stateTransition = TransitionStep(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
+ )
+ assertThat(isOnLockscreen).isTrue()
+
+ testScope.showCommunalScene()
+
+ // If bouncer is showing over the hub, it should not be considered on lockscreen
+ assertThat(isOnLockscreen).isFalse()
+ }
+
+ @Test
fun isOnLockscreenWithoutShade() =
testScope.runTest {
val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade)
@@ -1472,20 +1494,24 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
}
private fun TestScope.showCommunalScene() {
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalSceneRepository.setTransitionState(transitionState)
+ val targetScene =
+ if (SceneContainerFlag.isEnabled) {
+ Scenes.Communal
+ } else {
+ CommunalScenes.Communal
+ }
+ kosmos.communalSceneInteractor.changeScene(targetScene, "test")
runCurrent()
}
private fun TestScope.hideCommunalScene() {
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Blank)
- )
- communalSceneRepository.setTransitionState(transitionState)
+ val targetScene =
+ if (SceneContainerFlag.isEnabled) {
+ Scenes.Lockscreen
+ } else {
+ CommunalScenes.Blank
+ }
+ kosmos.communalSceneInteractor.changeScene(targetScene, "test")
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
index 9d17348d95a0..bab349aa7a74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor
import android.app.PendingIntent
-import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,12 +28,10 @@ import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler
-import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
@@ -52,10 +49,9 @@ import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+@EnableChipsModernization
class OngoingCallInteractorTest : SysuiTestCase() {
private val kosmos = Kosmos().useUnconfinedTestDispatcher()
- private val repository = kosmos.activeNotificationListRepository
private val underTest = kosmos.ongoingCallInteractor
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index d17abd7da05c..dbfd57fd0f0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.os.ParcelUuid
+import android.platform.test.annotations.EnableFlags
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
@@ -33,6 +34,8 @@ import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
@@ -897,6 +900,43 @@ class MobileIconsInteractorTest : SysuiTestCase() {
assertThat(latest).isEqualTo(2)
}
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_tracksNumberOfSubscriptions() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isStackable)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1))
+ assertThat(latest).isFalse()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ assertThat(latest).isTrue()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2, SUB_3_OPP))
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_checksForTerrestrialConnections() =
+ kosmos.runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ val latest by collectLastValue(underTest.isStackable)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ assertThat(latest).isTrue()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, exclusivelyNonTerrestrialSub))
+ assertThat(latest).isFalse()
+ }
+
/**
* Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
* flow.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
new file mode 100644
index 000000000000..20bdebd069f7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.pipeline.mobile.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StackedMobileIconViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+
+ private val Kosmos.underTest: StackedMobileIconViewModel by Fixture {
+ stackedMobileIconViewModel
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
+ kosmos.underTest.activateIn(testScope)
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_filtersOutNonDualConnections() =
+ kosmos.runTest {
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf()
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ assertThat(underTest.dualSim).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_filtersOutNonCellularIcons() =
+ kosmos.runTest {
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor
+ .getInteractorForSubId(SUB_1.subscriptionId)!!
+ .signalLevelIcon
+ .value =
+ SignalIconModel.Satellite(
+ level = 0,
+ icon = Icon.Resource(res = 0, contentDescription = null),
+ )
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ assertThat(underTest.dualSim).isNull()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_tracksActiveSubId() =
+ kosmos.runTest {
+ // Active sub id is null, order is unchanged
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ setIconLevel(SUB_1.subscriptionId, 1)
+ setIconLevel(SUB_2.subscriptionId, 2)
+
+ assertThat(underTest.dualSim!!.primary.level).isEqualTo(1)
+ assertThat(underTest.dualSim!!.secondary.level).isEqualTo(2)
+
+ // Active sub is 2, order is swapped
+ fakeMobileIconsInteractor.activeMobileDataSubscriptionId.value = SUB_2.subscriptionId
+
+ assertThat(underTest.dualSim!!.primary.level).isEqualTo(2)
+ assertThat(underTest.dualSim!!.secondary.level).isEqualTo(1)
+ }
+
+ private fun setIconLevel(subId: Int, level: Int) {
+ with(kosmos.fakeMobileIconsInteractor.getInteractorForSubId(subId)!!) {
+ signalLevelIcon.value =
+ (signalLevelIcon.value as SignalIconModel.Cellular).copy(level = level)
+ }
+ }
+
+ companion object {
+ private val SUB_1 =
+ SubscriptionModel(
+ subscriptionId = 1,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ private val SUB_2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = false,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ private val SUB_3 =
+ SubscriptionModel(
+ subscriptionId = 3,
+ isOpportunistic = false,
+ carrierName = "Carrier 3",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index eb961bd5f4ae..f91e3a612862 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -64,6 +64,8 @@ class FakeHomeStatusBarViewModel(
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
+ override val canShowOngoingActivityChips: Flow<Boolean> = MutableStateFlow(false)
+
override val batteryViewModelFactory: BatteryViewModel.Factory =
object : BatteryViewModel.Factory {
override fun create(): BatteryViewModel = mock(BatteryViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index cd8ca23e0964..8a796fc33608 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -86,6 +86,7 @@ import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataS
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarIconBlockList
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarInteractorShowOperatorName
@@ -677,6 +678,60 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
+ fun canShowOngoingActivityChips_statusBarHidden_noSecureCamera_noHun_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ // home status bar not allowed
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun canShowOngoingActivityChips_statusBarNotHidden_secureCamera_noHun_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope = testScope,
+ )
+ kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hun_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
fun isClockVisible_allowedByDisableFlags_visible() =
kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
@@ -835,7 +890,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ @EnableChipsModernization
fun isNotificationIconContainerVisible_anyChipShowing_ChipsModernizationOn() =
kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
index 8593f6a08b5a..605e4a47275b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
@@ -37,15 +37,19 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
@SmallTest
public class RotationLockControllerImplTest extends SysuiTestCase {
- private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"};
+ private static final String[] DEFAULT_SETTINGS = new String[]{"0:0", "1:2"};
- @Mock RotationPolicyWrapper mRotationPolicyWrapper;
- @Mock DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
+ @Mock
+ RotationPolicyWrapper mRotationPolicyWrapper;
+ @Mock
+ DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
private ArgumentCaptor<RotationPolicy.RotationPolicyListener> mRotationPolicyListenerCaptor;
@@ -93,7 +97,7 @@ public class RotationLockControllerImplTest extends SysuiTestCase {
private void createRotationLockController(String[] deviceStateRotationLockDefaults) {
new RotationLockControllerImpl(
mRotationPolicyWrapper,
- mDeviceStateRotationLockSettingController,
+ Optional.of(mDeviceStateRotationLockSettingController),
deviceStateRotationLockDefaults);
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index d3218ad8c9fb..a0bf7f00b11c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -203,6 +203,8 @@ public interface QS extends FragmentBase {
*/
void setIsNotificationPanelFullWidth(boolean isFullWidth);
+ default void setQSExpandingOrCollapsing(boolean isQSExpandingOrCollapsing) {}
+
/**
* Callback for when QSPanel container is scrolled
*/
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index 94fdbae83253..9b961d2535ae 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -122,7 +122,7 @@ public interface NotificationMenuRowPlugin extends Plugin {
public void setAppName(String appName);
- public void createMenu(ViewGroup parent, StatusBarNotification sbn);
+ public void createMenu(ViewGroup parent);
public void resetMenu();
@@ -215,9 +215,8 @@ public interface NotificationMenuRowPlugin extends Plugin {
/**
* Callback used to signal the menu that its parent notification has been updated.
- * @param sbn
*/
- public void onNotificationUpdated(StatusBarNotification sbn);
+ public void onNotificationUpdated();
/**
* Callback used to signal the menu that a user is moving the parent notification.
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3fc46ed6c9d1..359bd2bcb37c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3170,8 +3170,8 @@
<string name="controls_media_settings_button">Settings</string>
<!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
<string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
- <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
- <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
+ <!-- Content description for media controls progress bar [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1 hour 2 minutes 30 seconds">%1$s</xliff:g> of <xliff:g id="total_time" example="4 hours 5 seconds">%2$s</xliff:g></string>
<!-- Placeholder title to inform user that an app has posted media controls [CHAR_LIMIT=NONE] -->
<string name="controls_media_empty_title"><xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g> is running</string>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 0f1da509468a..ae3a76e2d2ca 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -71,6 +71,7 @@ android_library {
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:msdl",
"//frameworks/libs/systemui:view_capture",
+ "am_flags_lib",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml
index f22dac4b9fb4..98e5cea0e78f 100644
--- a/packages/SystemUI/shared/res/values/bools.xml
+++ b/packages/SystemUI/shared/res/values/bools.xml
@@ -22,4 +22,7 @@
<resources>
<!-- Whether to add padding at the bottom of the complication clock -->
<bool name="dream_overlay_complication_clock_bottom_padding">false</bool>
-</resources> \ No newline at end of file
+
+ <!-- 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 ed9ba7aa455b..487d1ce2514e 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,6 +46,8 @@ 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;
@@ -227,6 +229,17 @@ public class ActivityManagerWrapper {
}
/**
+ * Sets whether or not the specified task is perceptible.
+ */
+ public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) {
+ try {
+ return getService().setTaskIsPerceptible(taskId, isPerceptible);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Removes a task by id.
*/
public void removeTask(final int taskId) {
@@ -311,10 +324,23 @@ 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) {
return info.configuration.windowConfiguration.getActivityType()
== WindowConfiguration.ACTIVITY_TYPE_HOME;
}
+
+ public boolean isRunningInTestHarness() {
+ return ActivityManager.isRunningInTestHarness()
+ || ActivityManager.isRunningInUserTestHarness();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index 5e36539ecbec..a7bb11ea0442 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -156,7 +156,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
mMainExecutor.execute(() -> mView.updateEmergencyCallButton(
/* isInCall= */ isInCall,
/* hasTelephonyRadio= */ getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY),
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING),
/* simLocked= */ mKeyguardUpdateMonitor.isSimPinVoiceSecure(),
/* isSecure= */ isSecure));
});
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
index 7c141c1b561e..5247acc94e03 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
@@ -58,11 +58,22 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
private final BiMap<Integer, AmbientVolumeSlider> mSideToSliderMap = HashBiMap.create();
private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+ private HearingDevicesUiEventLogger mUiEventLogger;
+ private int mLaunchSourceId;
+
private final AmbientVolumeSlider.OnChangeListener mSliderOnChangeListener =
(slider, value) -> {
- if (mListener != null) {
- final int side = mSideToSliderMap.inverse().get(slider);
- mListener.onSliderValueChange(side, value);
+ final Integer side = mSideToSliderMap.inverse().get(slider);
+ if (side != null) {
+ if (mUiEventLogger != null) {
+ HearingDevicesUiEvent uiEvent = side == SIDE_UNIFIED
+ ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED
+ : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED;
+ mUiEventLogger.log(uiEvent, mLaunchSourceId);
+ }
+ if (mListener != null) {
+ mListener.onSliderValueChange(side, value);
+ }
}
};
@@ -94,6 +105,12 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
return;
}
setMuted(!mMuted);
+ if (mUiEventLogger != null) {
+ HearingDevicesUiEvent uiEvent = mMuted
+ ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_MUTE
+ : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_UNMUTE;
+ mUiEventLogger.log(uiEvent, mLaunchSourceId);
+ }
if (mListener != null) {
mListener.onAmbientVolumeIconClick();
}
@@ -103,6 +120,12 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
mExpandIcon = requireViewById(R.id.ambient_expand_icon);
mExpandIcon.setOnClickListener(v -> {
setExpanded(!mExpanded);
+ if (mUiEventLogger != null) {
+ HearingDevicesUiEvent uiEvent = mExpanded
+ ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS
+ : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS;
+ mUiEventLogger.log(uiEvent, mLaunchSourceId);
+ }
if (mListener != null) {
mListener.onExpandIconClick();
}
@@ -243,6 +266,11 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
updateVolumeLevel();
}
+ void setUiEventLogger(HearingDevicesUiEventLogger uiEventLogger, int launchSourceId) {
+ mUiEventLogger = uiEventLogger;
+ mLaunchSourceId = launchSourceId;
+ }
+
private void updateVolumeLevel() {
int leftLevel, rightLevel;
if (mExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 22ecb0af8c18..786d27af3994 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -382,6 +382,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) {
final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout);
+ ambientLayout.setUiEventLogger(mUiEventLogger, mLaunchSourceId);
mAmbientController = new AmbientVolumeUiController(
mDialog.getContext(), mLocalBluetoothManager, ambientLayout);
mAmbientController.setShowUiWhenLocalDataExist(false);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
index 9e77b02be495..fe1d5040c6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
@@ -29,7 +29,17 @@ enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnu
@UiEvent(doc = "Click on the device gear to enter device detail page")
HEARING_DEVICES_GEAR_CLICK(1853),
@UiEvent(doc = "Select a preset from preset spinner") HEARING_DEVICES_PRESET_SELECT(1854),
- @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856);
+ @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856),
+ @UiEvent(doc = "Change the ambient volume with unified control")
+ HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED(2149),
+ @UiEvent(doc = "Change the ambient volume with separated control")
+ HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED(2150),
+ @UiEvent(doc = "Mute the ambient volume") HEARING_DEVICES_AMBIENT_MUTE(2151),
+ @UiEvent(doc = "Unmute the ambient volume") HEARING_DEVICES_AMBIENT_UNMUTE(2152),
+ @UiEvent(doc = "Expand the ambient volume controls")
+ HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS(2153),
+ @UiEvent(doc = "Collapse the ambient volume controls")
+ HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154);
override fun getId(): Int = this.id
}
diff --git a/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS b/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS
index 429b4b0fccab..329aa07fdd9e 100644
--- a/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS
@@ -3,6 +3,5 @@
dupin@google.com
linyuh@google.com
pauldpong@google.com
-praveenj@google.com
vicliang@google.com
yuklimko@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b6537118324e..4c8a8f1c13d7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -33,6 +33,7 @@ import android.graphics.PixelFormat;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.PromptInfo;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -100,7 +101,7 @@ import javax.inject.Provider;
*/
@Deprecated
public class AuthContainerView extends LinearLayout
- implements AuthDialog, WakefulnessLifecycle.Observer, CredentialView.Host {
+ implements WakefulnessLifecycle.Observer, CredentialView.Host {
private static final String TAG = "AuthContainerView";
@@ -158,12 +159,11 @@ public class AuthContainerView extends LinearLayout
private final Set<Integer> mFailedModalities = new HashSet<Integer>();
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
- private final @Background DelayableExecutor mBackgroundExecutor;
private final MSDLPlayer mMSDLPlayer;
// Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
- @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
+ @Nullable @BiometricPrompt.DismissedReason private Integer mPendingCallbackReason;
// HAT received from LockSettingsService when credential is verified.
@Nullable private byte[] mCredentialAttestation;
@@ -188,18 +188,18 @@ public class AuthContainerView extends LinearLayout
final class BiometricCallback implements Spaghetti.Callback {
@Override
public void onAuthenticated() {
- animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
+ animateAway(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
}
@Override
public void onUserCanceled() {
sendEarlyUserCanceled();
- animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
}
@Override
public void onButtonNegative() {
- animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
+ animateAway(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
}
@Override
@@ -210,12 +210,12 @@ public class AuthContainerView extends LinearLayout
@Override
public void onContentViewMoreOptionsButtonPressed() {
- animateAway(AuthDialogCallback.DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS);
+ animateAway(BiometricPrompt.DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS);
}
@Override
public void onError() {
- animateAway(AuthDialogCallback.DISMISSED_ERROR);
+ animateAway(BiometricPrompt.DISMISSED_REASON_ERROR);
}
@Override
@@ -234,20 +234,20 @@ public class AuthContainerView extends LinearLayout
@Override
public void onAuthenticatedAndConfirmed() {
- animateAway(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
+ animateAway(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
}
}
@Override
public void onCredentialMatched(@NonNull byte[] attestation) {
mCredentialAttestation = attestation;
- animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
+ animateAway(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
}
@Override
public void onCredentialAborted() {
sendEarlyUserCanceled();
- animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
}
@Override
@@ -277,7 +277,7 @@ public class AuthContainerView extends LinearLayout
com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
null /* OnClickListener */)
.setOnDismissListener(
- dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
+ dialog -> animateAway(BiometricPrompt.DISMISSED_REASON_ERROR))
.create();
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
alertDialog.show();
@@ -349,7 +349,6 @@ public class AuthContainerView extends LinearLayout
mPanelView = mLayout.findViewById(R.id.panel);
mPanelController = new AuthPanelController(mContext, mPanelView);
- mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
mCredentialViewModelProvider = credentialViewModelProvider;
@@ -394,7 +393,7 @@ public class AuthContainerView extends LinearLayout
@VisibleForTesting
public void onBackInvoked() {
sendEarlyUserCanceled();
- animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
}
void sendEarlyUserCanceled() {
@@ -402,7 +401,6 @@ public class AuthContainerView extends LinearLayout
BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL, getRequestId());
}
- @Override
public boolean isAllowDeviceCredentials() {
return Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo);
}
@@ -450,7 +448,6 @@ public class AuthContainerView extends LinearLayout
mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight());
}
- @Override
public void onOrientationChanged() {
}
@@ -538,10 +535,9 @@ public class AuthContainerView extends LinearLayout
@Override
public void onStartedGoingToSleep() {
- animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
}
- @Override
public void show(WindowManager wm) {
wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
}
@@ -559,7 +555,6 @@ public class AuthContainerView extends LinearLayout
}
}
- @Override
public void dismissWithoutCallback(boolean animate) {
if (animate) {
animateAway(false /* sendReason */, 0 /* reason */);
@@ -569,12 +564,10 @@ public class AuthContainerView extends LinearLayout
}
}
- @Override
public void dismissFromSystemServer() {
animateAway(false /* sendReason */, 0 /* reason */);
}
- @Override
public void onAuthenticationSucceeded(@Modality int modality) {
if (mBiometricView != null) {
mBiometricView.onAuthenticationSucceeded(modality);
@@ -583,7 +576,6 @@ public class AuthContainerView extends LinearLayout
}
}
- @Override
public void onAuthenticationFailed(@Modality int modality, String failureReason) {
if (mBiometricView != null) {
mFailedModalities.add(modality);
@@ -593,7 +585,6 @@ public class AuthContainerView extends LinearLayout
}
}
- @Override
public void onHelp(@Modality int modality, String help) {
if (mBiometricView != null) {
mBiometricView.onHelp(modality, help);
@@ -602,7 +593,6 @@ public class AuthContainerView extends LinearLayout
}
}
- @Override
public void onError(@Modality int modality, String error) {
if (mBiometricView != null) {
mBiometricView.onError(modality, error);
@@ -611,7 +601,6 @@ public class AuthContainerView extends LinearLayout
}
}
- @Override
public void onPointerDown() {
if (mBiometricView != null) {
if (mFailedModalities.contains(TYPE_FACE)) {
@@ -624,22 +613,18 @@ public class AuthContainerView extends LinearLayout
}
}
- @Override
public String getOpPackageName() {
return mConfig.mOpPackageName;
}
- @Override
public String getClassNameIfItIsConfirmDeviceCredentialActivity() {
return mConfig.mPromptInfo.getClassNameIfItIsConfirmDeviceCredentialActivity();
}
- @Override
public long getRequestId() {
return mConfig.mRequestId;
}
- @Override
public void animateToCredentialUI(boolean isError) {
if (mBiometricView != null) {
mBiometricView.startTransitionToCredentialUI(isError);
@@ -648,11 +633,11 @@ public class AuthContainerView extends LinearLayout
}
}
- void animateAway(@AuthDialogCallback.DismissedReason int reason) {
+ void animateAway(@BiometricPrompt.DismissedReason int reason) {
animateAway(true /* sendReason */, reason);
}
- private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) {
+ private void animateAway(boolean sendReason, @BiometricPrompt.DismissedReason int reason) {
if (mContainerState == STATE_ANIMATING_IN) {
Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
mContainerState = STATE_PENDING_DISMISS;
@@ -732,7 +717,7 @@ public class AuthContainerView extends LinearLayout
private void onDialogAnimatedIn() {
if (mContainerState == STATE_PENDING_DISMISS) {
Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
- animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
return;
}
if (mContainerState == STATE_ANIMATING_OUT || mContainerState == STATE_GONE) {
@@ -748,7 +733,6 @@ public class AuthContainerView extends LinearLayout
}
}
- @Override
public PromptViewModel getViewModel() {
return mPromptViewModel;
}
@@ -776,7 +760,6 @@ public class AuthContainerView extends LinearLayout
return lp;
}
- @Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println(" isAttachedToWindow=" + isAttachedToWindow());
pw.println(" containerState=" + mContainerState);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index eee5f9e34317..68a282018ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -163,7 +163,7 @@ public class AuthController implements
// TODO: These should just be saved from onSaveState
private SomeArgs mCurrentDialogArgs;
@VisibleForTesting
- AuthDialog mCurrentDialog;
+ AuthContainerView mCurrentDialog;
@NonNull private final WindowManager mWindowManager;
@NonNull private final DisplayManager mDisplayManager;
@@ -222,7 +222,7 @@ public class AuthController implements
closeDialog(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, reasonString);
}
- private void closeDialog(@DismissedReason int reason, String reasonString) {
+ private void closeDialog(@BiometricPrompt.DismissedReason int reason, String reasonString) {
if (isShowing()) {
Log.i(TAG, "Close BP, reason :" + reasonString);
mCurrentDialog.dismissWithoutCallback(true /* animate */);
@@ -511,60 +511,14 @@ public class AuthController implements
}
@Override
- public void onDismissed(@DismissedReason int reason,
- @Nullable byte[] credentialAttestation, long requestId) {
-
+ public void onDismissed(@BiometricPrompt.DismissedReason int reason,
+ @Nullable byte[] credentialAttestation, long requestId) {
if (mCurrentDialog != null && requestId != mCurrentDialog.getRequestId()) {
Log.w(TAG, "requestId doesn't match, skip onDismissed");
return;
}
- switch (reason) {
- case AuthDialogCallback.DISMISSED_USER_CANCELED:
- sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
- credentialAttestation);
- break;
-
- case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
- sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE,
- credentialAttestation);
- break;
-
- case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
- sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED,
- credentialAttestation);
- break;
-
- case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED:
- sendResultAndCleanUp(
- BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
- credentialAttestation);
- break;
-
- case AuthDialogCallback.DISMISSED_ERROR:
- sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR,
- credentialAttestation);
- break;
-
- case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
- sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED,
- credentialAttestation);
- break;
-
- case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED:
- sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED,
- credentialAttestation);
- break;
-
- case AuthDialogCallback.DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS:
- sendResultAndCleanUp(
- BiometricPrompt.DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS,
- credentialAttestation);
- break;
- default:
- Log.e(TAG, "Unhandled reason: " + reason);
- break;
- }
+ sendResultAndCleanUp(reason, credentialAttestation);
}
@Override
@@ -699,7 +653,7 @@ public class AuthController implements
mUdfpsController.onAodInterrupt(screenX, screenY, major, minor);
}
- private void sendResultAndCleanUp(@DismissedReason int reason,
+ private void sendResultAndCleanUp(@BiometricPrompt.DismissedReason int reason,
@Nullable byte[] credentialAttestation) {
if (mReceiver == null) {
Log.e(TAG, "sendResultAndCleanUp: Receiver is null");
@@ -1244,7 +1198,7 @@ public class AuthController implements
final long requestId = args.argl2;
// Create a new dialog but do not replace the current one yet.
- final AuthDialog newDialog = buildDialog(
+ final AuthContainerView newDialog = buildDialog(
mBackgroundExecutor,
promptInfo,
requireConfirmation,
@@ -1327,7 +1281,7 @@ public class AuthController implements
return mContext.createDisplayContext(display).getSystemService(WindowManager.class);
}
- private void onDialogDismissed(@DismissedReason int reason) {
+ private void onDialogDismissed(@BiometricPrompt.DismissedReason int reason) {
if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
if (mCurrentDialog == null) {
Log.w(TAG, "Dialog already dismissed");
@@ -1361,7 +1315,7 @@ public class AuthController implements
}
}
- protected AuthDialog buildDialog(@Background DelayableExecutor bgExecutor,
+ protected AuthContainerView buildDialog(@Background DelayableExecutor bgExecutor,
PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds,
String opPackageName, boolean skipIntro, long operationId, long requestId,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@@ -1389,7 +1343,7 @@ public class AuthController implements
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- final AuthDialog dialog = mCurrentDialog;
+ final AuthContainerView dialog = mCurrentDialog;
pw.println(" mCachedDisplayInfo=" + mCachedDisplayInfo);
pw.println(" mScaleFactor=" + mScaleFactor);
pw.println(" fingerprintSensorLocationInNaturalOrientation="
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
deleted file mode 100644
index 861191671ba9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.view.WindowManager;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
-
-/**
- * Interface for the biometric dialog UI.
- *
- * TODO(b/287311775): remove along with legacy controller once flag is removed
- */
-@Deprecated
-public interface AuthDialog extends Dumpable {
-
- /**
- * Parameters used when laying out {@link AuthBiometricView}, its subclasses, and
- * {@link AuthPanelController}.
- */
- class LayoutParams {
- public final int mMediumHeight;
- public final int mMediumWidth;
-
- public LayoutParams(int mediumWidth, int mediumHeight) {
- mMediumWidth = mediumWidth;
- mMediumHeight = mediumHeight;
- }
- }
-
- /**
- * Show the dialog.
- * @param wm
- */
- void show(WindowManager wm);
-
- /**
- * Dismiss the dialog without sending a callback.
- */
- void dismissWithoutCallback(boolean animate);
-
- /**
- * Dismiss the dialog. Animate away.
- */
- void dismissFromSystemServer();
-
- /**
- * Biometric authenticated. May be pending user confirmation, or completed.
- */
- void onAuthenticationSucceeded(@Modality int modality);
-
- /**
- * Authentication failed (reject, timeout). Dialog stays showing.
- * @param modality sensor modality that triggered the error
- * @param failureReason message
- */
- void onAuthenticationFailed(@Modality int modality, String failureReason);
-
- /**
- * Authentication rejected, or help message received.
- * @param modality sensor modality that triggered the help message
- * @param help message
- */
- void onHelp(@Modality int modality, String help);
-
- /**
- * Authentication failed. Dialog going away.
- * @param modality sensor modality that triggered the error
- * @param error message
- */
- void onError(@Modality int modality, String error);
-
- /** UDFPS pointer down event. */
- void onPointerDown();
-
- /**
- * Get the client's package name
- */
- String getOpPackageName();
-
- /**
- * Get the class name of ConfirmDeviceCredentialActivity. Returns null if the direct caller is
- * not ConfirmDeviceCredentialActivity.
- */
- String getClassNameIfItIsConfirmDeviceCredentialActivity();
-
- /** The requestId of the underlying operation within the framework. */
- long getRequestId();
-
- /**
- * Animate to credential UI. Typically called after biometric is locked out.
- */
- void animateToCredentialUI(boolean isError);
-
- /**
- * @return true if device credential is allowed.
- */
- boolean isAllowDeviceCredentials();
-
- /**
- * Called when the device's orientation changed and the dialog may need to do another
- * layout. This is most relevant to UDFPS since configuration changes are not sent by
- * the framework in equivalent cases (landscape to reverse landscape) but the dialog
- * must remain fixed on the physical sensor location.
- */
- void onOrientationChanged();
-
- PromptViewModel getViewModel();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index 024c6eaa75bb..31c63d3c57e0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -16,40 +16,20 @@
package com.android.systemui.biometrics;
-import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricPrompt;
/**
* Callback interface for dialog views. These should be implemented by the controller (e.g.
* FingerprintDialogImpl) and passed into their views (e.g. FingerprintDialogView).
*/
public interface AuthDialogCallback {
-
- int DISMISSED_USER_CANCELED = 1;
- int DISMISSED_BUTTON_NEGATIVE = 2;
- int DISMISSED_BUTTON_POSITIVE = 3;
- int DISMISSED_BIOMETRIC_AUTHENTICATED = 4;
- int DISMISSED_ERROR = 5;
- int DISMISSED_BY_SYSTEM_SERVER = 6;
- int DISMISSED_CREDENTIAL_AUTHENTICATED = 7;
- int DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS = 8;
-
- @IntDef({DISMISSED_USER_CANCELED,
- DISMISSED_BUTTON_NEGATIVE,
- DISMISSED_BUTTON_POSITIVE,
- DISMISSED_BIOMETRIC_AUTHENTICATED,
- DISMISSED_ERROR,
- DISMISSED_BY_SYSTEM_SERVER,
- DISMISSED_CREDENTIAL_AUTHENTICATED,
- DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS})
- @interface DismissedReason {}
-
/**
* Invoked when the dialog is dismissed
- * @param reason
+ * @param reason - the {@link BiometricPrompt.DismissedReason} for dismissing
* @param credentialAttestation the HAT received from LockSettingsService upon verification
*/
- void onDismissed(@DismissedReason int reason,
+ void onDismissed(@BiometricPrompt.DismissedReason int reason,
@Nullable byte[] credentialAttestation, long requestId);
/**
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 cce1ae1a2947..6473b1c30586 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
@@ -237,7 +237,15 @@ constructor(
with(mediaHost) {
expansion = MediaHostState.EXPANDED
expandedMatchesParentHeight = true
- showsOnlyActiveMedia = false
+ if (v2FlagEnabled()) {
+ // Only show active media to match lock screen, not resumable media, which can
+ // persist
+ // for up to 2 days.
+ showsOnlyActiveMedia = true
+ } else {
+ // Maintain old behavior on tablet until V2 flag rolls out.
+ showsOnlyActiveMedia = false
+ }
falsingProtectionNeeded = false
disablePagination = true
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 3c68e3a09f02..a25faa3a7aec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -56,6 +56,7 @@ import com.android.systemui.reardisplay.RearDisplayModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.recents.RecentsModule;
+import com.android.systemui.rotationlock.DeviceStateAutoRotateModule;
import com.android.systemui.rotationlock.RotationLockModule;
import com.android.systemui.rotationlock.RotationLockNewModule;
import com.android.systemui.scene.SceneContainerFrameworkModule;
@@ -132,6 +133,7 @@ import javax.inject.Named;
CollapsedStatusBarFragmentStartableModule.class,
ConnectingDisplayViewModel.StartableModule.class,
DefaultBlueprintModule.class,
+ DeviceStateAutoRotateModule.class,
EmergencyGestureModule.class,
GestureModule.class,
HeadsUpModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index fe5a82cb5b8c..f8cf6b007041 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -105,6 +105,7 @@ import com.android.systemui.qs.footer.dagger.FooterActionsModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.recordissue.RecordIssueModule;
import com.android.systemui.retail.RetailModeModule;
+import com.android.systemui.rotationlock.DeviceStateAutoRotateModule.BoundsDeviceStateAutoRotateModule;
import com.android.systemui.scene.shared.model.SceneContainerConfig;
import com.android.systemui.scene.shared.model.SceneDataSource;
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator;
@@ -145,6 +146,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerModule;
import com.android.systemui.statusbar.phone.LetterboxModule;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
+import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.PolicyModule;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
@@ -391,6 +393,11 @@ public abstract class SystemUIModule {
@BindsOptionalOf
abstract LockscreenContent optionalLockscreenContent();
+ @BindsOptionalOf
+ @BoundsDeviceStateAutoRotateModule
+ abstract Optional<DeviceStateRotationLockSettingController>
+ optionalDeviceStateRotationLockSettingController();
+
@SysUISingleton
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
@@ -466,6 +473,16 @@ public abstract class SystemUIModule {
return new SceneDataSourceDelegator(applicationScope, config);
}
+ @Provides
+ @SysUISingleton
+ static Optional<DeviceStateRotationLockSettingController>
+ provideDeviceStateRotationLockSettingController(
+ @BoundsDeviceStateAutoRotateModule
+ Optional<Optional<DeviceStateRotationLockSettingController>> optionalOfOptional
+ ) {
+ return optionalOfOptional.orElseGet(Optional::empty);
+ }
+
@Binds
abstract SceneDataSource bindSceneDataSource(SceneDataSourceDelegator delegator);
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index 0908e3b5bf85..a779c4c998a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -31,7 +31,6 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
@@ -67,7 +66,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to System,
KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to System,
KEY_GESTURE_TYPE_LOCK_SCREEN to System,
- KEY_GESTURE_TYPE_OPEN_NOTES to System,
KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to System,
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to System,
KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to System,
@@ -113,7 +111,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
R.string.shortcut_helper_category_system_controls,
KEY_GESTURE_TYPE_LOCK_SCREEN to R.string.shortcut_helper_category_system_controls,
KEY_GESTURE_TYPE_ALL_APPS to R.string.shortcut_helper_category_system_controls,
- KEY_GESTURE_TYPE_OPEN_NOTES to R.string.shortcut_helper_category_system_apps,
KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to
R.string.shortcut_helper_category_system_apps,
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.shortcut_helper_category_system_apps,
@@ -173,7 +170,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
R.string.group_system_access_notification_shade,
KEY_GESTURE_TYPE_LOCK_SCREEN to R.string.group_system_lock_screen,
KEY_GESTURE_TYPE_ALL_APPS to R.string.group_system_access_all_apps_search,
- KEY_GESTURE_TYPE_OPEN_NOTES to R.string.group_system_quick_memo,
KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to R.string.group_system_access_system_settings,
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.group_system_access_google_assistant,
KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index 8bed8537b6c5..a7375f7e7efc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -178,11 +178,6 @@ constructor(@Main private val resources: Resources, private val inputManager: In
private fun systemAppsShortcuts() =
listOf(
- // Pull up Notes app for quick memo:
- // - Meta + Ctrl + N
- shortcutInfo(resources.getString(R.string.group_system_quick_memo)) {
- command(META_META_ON or META_CTRL_ON, KEYCODE_N)
- },
// Access system settings:
// - Meta + I
shortcutInfo(resources.getString(R.string.group_system_access_system_settings)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 4ad04bef6836..ef06a85bd0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -20,6 +20,10 @@ import android.animation.ValueAnimator
import android.util.Log
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -54,6 +58,9 @@ constructor(
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+ private val communalInteractor: CommunalInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
@@ -103,6 +110,7 @@ constructor(
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val biometricUnlockMode = keyguardInteractor.biometricUnlockState.value.mode
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
+ val shouldShowCommunal = communalInteractor.shouldShowCommunal.value
if (!maybeHandleInsecurePowerGesture()) {
val shouldTransitionToLockscreen =
@@ -129,6 +137,9 @@ constructor(
(!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen()) ||
(KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone)
+ val shouldTransitionToCommunal =
+ communalSettingsInteractor.isV2FlagEnabled() && shouldShowCommunal
+
if (shouldTransitionToGone) {
// TODO(b/360368320): Adapt for scene framework
if (SceneContainerFlag.isEnabled) return@collect
@@ -137,6 +148,11 @@ constructor(
modeOnCanceled = TransitionModeOnCanceled.REVERSE,
ownerReason = "canWakeDirectlyToGone = true",
)
+ } else if (shouldTransitionToCommunal) {
+ communalSceneInteractor.changeScene(
+ CommunalScenes.Communal,
+ "listen for aod to communal",
+ )
} else if (shouldTransitionToLockscreen) {
val modeOnCanceled =
if (startedStep.from == KeyguardState.LOCKSCREEN) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 6f5f662d6fa3..0700ec639153 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -159,6 +159,7 @@ constructor(
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
+ val canStartDreaming = dreamManager.canStartDreaming(false)
if (!deviceEntryInteractor.isLockscreenEnabled()) {
if (!SceneContainerFlag.isEnabled) {
@@ -191,6 +192,13 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
+ } else if (canStartDreaming) {
+ // If we're waking up to dream, transition directly to dreaming without
+ // showing the lockscreen.
+ startTransitionTo(
+ KeyguardState.DREAMING,
+ ownerReason = "moving from doze to dream",
+ )
} else {
startTransitionTo(KeyguardState.LOCKSCREEN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 2eeba0f10e0c..3ad862b761fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -77,7 +77,7 @@ constructor(
if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
return
}
- listenForHubToDozing()
+ listenForHubToAodOrDozing()
listenForHubToPrimaryBouncer()
listenForHubToAlternateBouncer()
listenForHubToOccluded()
@@ -123,15 +123,15 @@ constructor(
}
}
- private fun listenForHubToDozing() {
+ private fun listenForHubToAodOrDozing() {
scope.launch {
powerInteractor.isAsleep
.filterRelevantKeyguardStateAnd { isAsleep -> isAsleep }
.collect {
- communalSceneInteractor.snapToScene(
+ communalSceneInteractor.changeScene(
newScene = CommunalScenes.Blank,
- loggingReason = "hub to dozing",
- keyguardState = KeyguardState.DOZING,
+ loggingReason = "hub to sleep",
+ keyguardState = keyguardInteractor.asleepKeyguardState.value,
)
}
}
@@ -254,5 +254,6 @@ constructor(
val TO_LOCKSCREEN_DURATION = 1.seconds
val TO_BOUNCER_DURATION = 400.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
+ val TO_AOD_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index dba2578f79da..c4a7e1ed95e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -21,6 +21,7 @@ import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransiti
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
@@ -33,6 +34,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransiti
import com.android.systemui.keyguard.ui.viewmodel.DreamingToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -118,6 +120,12 @@ abstract class DeviceEntryIconTransitionModule {
@Binds
@IntoSet
+ abstract fun aodToGlanceableHub(
+ impl: AodToGlanceableHubTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun dozingToGone(impl: DozingToGoneTransitionViewModel): DeviceEntryIconTransition
@Binds
@@ -258,6 +266,12 @@ abstract class DeviceEntryIconTransitionModule {
@Binds
@IntoSet
+ abstract fun glanceableHubToAod(
+ impl: GlanceableHubToAodTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun occludedToGlanceableHub(
impl: OccludedToGlanceableHubTransitionViewModel
): DeviceEntryIconTransition
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
new file mode 100644
index 000000000000..45f8f10595e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import 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
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+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 javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class AodToGlanceableHubTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+ blurFactory: GlanceableHubBlurComponent.Factory,
+) : DeviceEntryIconTransition, GlanceableHubTransition {
+ private val transitionAnimation =
+ animationFlow
+ .setup(
+ duration = TransitionDuration.TO_GLANCEABLE_HUB_DURATION_MS.milliseconds,
+ edge = Edge.create(AOD, Scenes.Communal),
+ )
+ .setupWithoutSceneContainer(edge = Edge.create(AOD, GLANCEABLE_HUB))
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+
+ /** Fade out the lockscreen during a transition to GLANCEABLE_HUB. */
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var currentAlpha = 0f
+ return transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ startTime =
+ if (lightRevealMigration()) {
+ 100.milliseconds // Wait for the light reveal to "hit" the LS elements.
+ } else {
+ 0.milliseconds
+ },
+ onStart = {
+ currentAlpha =
+ if (lightRevealMigration()) {
+ viewState.alpha()
+ } else {
+ 0f
+ }
+ },
+ onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+ )
+ }
+
+ override val windowBlurRadius: Flow<Float> =
+ blurFactory.create(transitionAnimation).getBlurProvider().enterBlurRadius
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 75bba489f893..0ccb24a9858a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -60,6 +60,7 @@ constructor(
primaryBouncerToDozingTransitionViewModel: PrimaryBouncerToDozingTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
+ glanceableHubToAodTransitionViewModel: GlanceableHubToAodTransitionViewModel,
) {
val color: Flow<Int> =
deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground ->
@@ -106,6 +107,7 @@ constructor(
primaryBouncerToLockscreenTransitionViewModel
.deviceEntryBackgroundViewAlpha,
lockscreenToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ glanceableHubToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
.onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
index e6a85c6860c5..9018c58a7e36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt
@@ -39,4 +39,6 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) {
)
val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
+ // Notifications should not be shown while transitioning to dream.
+ val notificationAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToAodTransitionViewModel.kt
new file mode 100644
index 000000000000..6a45845a02c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToAodTransitionViewModel.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class GlanceableHubToAodTransitionViewModel
+@Inject
+constructor(
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+
+ private val transitionAnimation =
+ animationFlow
+ .setup(
+ duration = FromGlanceableHubTransitionInteractor.TO_AOD_DURATION,
+ edge = Edge.create(from = Scenes.Communal, to = AOD),
+ )
+ .setupWithoutSceneContainer(edge = Edge.create(KeyguardState.GLANCEABLE_HUB, AOD))
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ )
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
+ ->
+ if (udfpsEnrolledAndEnabled) {
+ transitionAnimation.immediatelyTransitionTo(1f)
+ } else {
+ emptyFlow()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 8e21745e1a31..def87a8e2717 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -100,6 +100,7 @@ constructor(
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
+ private val aodToGlanceableHubTransitionViewModel: AodToGlanceableHubTransitionViewModel,
private val dozingToDreamingTransitionViewModel: DozingToDreamingTransitionViewModel,
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
@@ -111,6 +112,7 @@ constructor(
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
+ private val glanceableHubToAodTransitionViewModel: GlanceableHubToAodTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
private val goneToDreamingTransitionViewModel: GoneToDreamingTransitionViewModel,
@@ -258,6 +260,7 @@ constructor(
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
aodToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
+ aodToGlanceableHubTransitionViewModel.lockscreenAlpha(viewState),
dozingToDreamingTransitionViewModel.lockscreenAlpha,
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
@@ -267,6 +270,7 @@ constructor(
dreamingToGoneTransitionViewModel.lockscreenAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ glanceableHubToAodTransitionViewModel.lockscreenAlpha,
goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
goneToDozingTransitionViewModel.lockscreenAlpha,
goneToDreamingTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 6d796d96ea71..3f538203aee9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -49,9 +49,10 @@ import com.android.systemui.media.controls.ui.util.MediaArtworkHelper
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaActionViewModel
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
-import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_CENTER_ALPHA
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_ALL
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_COMPACT
import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel
@@ -537,18 +538,24 @@ object MediaControlViewBinder {
height: Int,
): LayerDrawable {
val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
- val alpha =
+ val startAlpha =
if (Flags.mediaControlsA11yColors()) {
- MEDIA_PLAYER_SCRIM_CENTER_ALPHA
- } else {
MEDIA_PLAYER_SCRIM_START_ALPHA
+ } else {
+ MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY
+ }
+ val endAlpha =
+ if (Flags.mediaControlsA11yColors()) {
+ MEDIA_PLAYER_SCRIM_END_ALPHA
+ } else {
+ MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY
}
return MediaArtworkHelper.setUpGradientColorOnDrawable(
albumArt,
context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
mutableColorScheme,
- alpha,
- MEDIA_PLAYER_SCRIM_END_ALPHA,
+ startAlpha,
+ endAlpha,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
index 34f7c4dcaec0..c9716be52408 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
@@ -18,6 +18,9 @@ package com.android.systemui.media.controls.ui.binder
import android.animation.Animator
import android.animation.ObjectAnimator
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
import android.text.format.DateUtils
import androidx.annotation.UiThread
import androidx.lifecycle.Observer
@@ -28,8 +31,11 @@ import com.android.systemui.media.controls.ui.drawable.SquigglyProgress
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
+import java.util.Locale
private const val TAG = "SeekBarObserver"
+private const val MIN_IN_SEC = 60
+private const val HOUR_IN_SEC = MIN_IN_SEC * 60
/**
* Observer for changes from SeekBarViewModel.
@@ -127,10 +133,9 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
}
holder.seekBar.setMax(data.duration)
- val totalTimeString =
- DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS)
+ val totalTimeDescription = formatTimeContentDescription(data.duration)
if (data.scrubbing) {
- holder.scrubbingTotalTimeView.text = totalTimeString
+ holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration)
}
data.elapsedTime?.let {
@@ -148,20 +153,62 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
}
}
- val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS)
+ val elapsedTimeDescription = formatTimeContentDescription(it)
if (data.scrubbing) {
- holder.scrubbingElapsedTimeView.text = elapsedTimeString
+ holder.scrubbingElapsedTimeView.text = formatTimeLabel(it)
}
holder.seekBar.contentDescription =
holder.seekBar.context.getString(
R.string.controls_media_seekbar_description,
- elapsedTimeString,
- totalTimeString
+ elapsedTimeDescription,
+ totalTimeDescription,
)
}
}
+ /** Returns a time string suitable for display, e.g. "12:34" */
+ private fun formatTimeLabel(milliseconds: Int): CharSequence {
+ return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS)
+ }
+
+ /**
+ * Returns a time string suitable for content description, e.g. "12 minutes 34 seconds"
+ *
+ * Follows same logic as Chronometer#formatDuration
+ */
+ private fun formatTimeContentDescription(milliseconds: Int): CharSequence {
+ var seconds = milliseconds / DateUtils.SECOND_IN_MILLIS
+
+ val hours =
+ if (seconds >= HOUR_IN_SEC) {
+ seconds / HOUR_IN_SEC
+ } else {
+ 0
+ }
+ seconds -= hours * HOUR_IN_SEC
+
+ val minutes =
+ if (seconds >= MIN_IN_SEC) {
+ seconds / MIN_IN_SEC
+ } else {
+ 0
+ }
+ seconds -= minutes * MIN_IN_SEC
+
+ val measures = arrayListOf<Measure>()
+ if (hours > 0) {
+ measures.add(Measure(hours, MeasureUnit.HOUR))
+ }
+ if (minutes > 0) {
+ measures.add(Measure(minutes, MeasureUnit.MINUTE))
+ }
+ measures.add(Measure(seconds, MeasureUnit.SECOND))
+
+ return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(*measures.toTypedArray())
+ }
+
@VisibleForTesting
open fun buildResetAnimator(targetTime: Int): Animator {
val animator =
@@ -169,7 +216,7 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
holder.seekBar,
"progress",
holder.seekBar.progress,
- targetTime + RESET_ANIMATION_DURATION_MS
+ targetTime + RESET_ANIMATION_DURATION_MS,
)
animator.setAutoCancel(true)
animator.duration = RESET_ANIMATION_DURATION_MS.toLong()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 39c08daf53d6..694a4c7e493d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -23,7 +23,10 @@ import static com.android.systemui.Flags.communalHub;
import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation;
import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions;
import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
-import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_CENTER_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -176,9 +179,7 @@ public class MediaControlPanel {
protected static final int SMARTSPACE_CARD_DISMISS_EVENT = 761;
private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
- private static final float MEDIA_SCRIM_START_ALPHA = 0.25f;
private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
- private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f;
private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
@@ -1093,11 +1094,12 @@ public class MediaControlPanel {
Drawable albumArt = getScaledBackground(artworkIcon, width, height);
GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
R.drawable.qs_media_scrim).mutate();
- float startAlpha = (Flags.mediaControlsA11yColors())
- ? MEDIA_PLAYER_SCRIM_CENTER_ALPHA
- : MEDIA_SCRIM_START_ALPHA;
+ if (Flags.mediaControlsA11yColors()) {
+ return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
+ MEDIA_PLAYER_SCRIM_START_ALPHA, MEDIA_PLAYER_SCRIM_END_ALPHA);
+ }
return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
- startAlpha, MEDIA_PLAYER_SCRIM_END_ALPHA);
+ MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY, MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY);
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 198155b3f297..b687dce20b06 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -1137,6 +1137,7 @@ constructor(
) {
gutsViewHolder.gutsText.setTypeface(menuTF)
gutsViewHolder.dismissText.setTypeface(menuTF)
+ gutsViewHolder.cancelText.setTypeface(menuTF)
titleText.setTypeface(titleTF)
artistText.setTypeface(artistTF)
seamlessText.setTypeface(menuTF)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 9153e17393d2..bcda485272c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -419,8 +419,10 @@ class MediaControlViewModel(
const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L
@Deprecated("Remove with media_controls_a11y_colors flag")
- const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f
- const val MEDIA_PLAYER_SCRIM_CENTER_ALPHA = 0.75f
- const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f
+ const val MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY = 0.25f
+ @Deprecated("Remove with media_controls_a11y_colors flag")
+ const val MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY = 1.0f
+ const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.65f
+ const val MEDIA_PLAYER_SCRIM_END_ALPHA = 0.75f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
index 24bb16a11fe7..3a81102699f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -27,9 +27,7 @@ object QSEvents {
private set
fun setLoggerForTesting(): UiEventLoggerFake {
- return UiEventLoggerFake().also {
- qsUiEventsLogger = it
- }
+ return UiEventLoggerFake().also { qsUiEventsLogger = it }
}
fun resetLogger() {
@@ -40,32 +38,28 @@ object QSEvents {
enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "Tile clicked. It has an instance id and a spec (or packageName)")
QS_ACTION_CLICK(387),
-
- @UiEvent(doc = "Tile secondary button clicked. " +
- "It has an instance id and a spec (or packageName)")
+ @UiEvent(
+ doc =
+ "Tile secondary button clicked. " + "It has an instance id and a spec (or packageName)"
+ )
QS_ACTION_SECONDARY_CLICK(388),
-
@UiEvent(doc = "Tile long clicked. It has an instance id and a spec (or packageName)")
QS_ACTION_LONG_PRESS(389),
-
- @UiEvent(doc = "Quick Settings panel expanded")
- QS_PANEL_EXPANDED(390),
-
- @UiEvent(doc = "Quick Settings panel collapsed")
- QS_PANEL_COLLAPSED(391),
-
- @UiEvent(doc = "Tile visible in Quick Settings panel. The tile may be in a different page. " +
- "It has an instance id and a spec (or packageName)")
+ @UiEvent(doc = "Quick Settings panel expanded") QS_PANEL_EXPANDED(390),
+ @UiEvent(doc = "Quick Settings panel collapsed") QS_PANEL_COLLAPSED(391),
+ @UiEvent(
+ doc =
+ "Tile visible in Quick Settings panel. The tile may be in a different page. " +
+ "It has an instance id and a spec (or packageName)"
+ )
QS_TILE_VISIBLE(392),
-
- @UiEvent(doc = "Quick Quick Settings panel expanded")
- QQS_PANEL_EXPANDED(393),
-
- @UiEvent(doc = "Quick Quick Settings panel collapsed")
- QQS_PANEL_COLLAPSED(394),
-
- @UiEvent(doc = "Tile visible in Quick Quick Settings panel. " +
- "It has an instance id and a spec (or packageName)")
+ @UiEvent(doc = "Quick Quick Settings panel expanded") QQS_PANEL_EXPANDED(393),
+ @UiEvent(doc = "Quick Quick Settings panel collapsed") QQS_PANEL_COLLAPSED(394),
+ @UiEvent(
+ doc =
+ "Tile visible in Quick Quick Settings panel. " +
+ "It has an instance id and a spec (or packageName)"
+ )
QQS_TILE_VISIBLE(395);
override fun getId() = _id
@@ -73,47 +67,32 @@ enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
enum class QSEditEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
- @UiEvent(doc = "Tile removed from current tiles")
- QS_EDIT_REMOVE(210),
-
- @UiEvent(doc = "Tile added to current tiles")
- QS_EDIT_ADD(211),
-
- @UiEvent(doc = "Tile moved")
- QS_EDIT_MOVE(212),
-
- @UiEvent(doc = "QS customizer open")
- QS_EDIT_OPEN(213),
-
- @UiEvent(doc = "QS customizer closed")
- QS_EDIT_CLOSED(214),
-
- @UiEvent(doc = "QS tiles reset")
- QS_EDIT_RESET(215);
+ @UiEvent(doc = "Tile removed from current tiles") QS_EDIT_REMOVE(210),
+ @UiEvent(doc = "Tile added to current tiles") QS_EDIT_ADD(211),
+ @UiEvent(doc = "Tile moved") QS_EDIT_MOVE(212),
+ @UiEvent(doc = "QS customizer open") QS_EDIT_OPEN(213),
+ @UiEvent(doc = "QS customizer closed") QS_EDIT_CLOSED(214),
+ @UiEvent(doc = "QS tiles reset") QS_EDIT_RESET(215),
+ @UiEvent(doc = "QS edit mode resize tile to large") QS_EDIT_RESIZE_LARGE(2122),
+ @UiEvent(doc = "QS edit mode resize tile to small") QS_EDIT_RESIZE_SMALL(2123);
override fun getId() = _id
}
/**
- * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger}
- * Other names for DND (Do Not Disturb) include "Zen" and "Priority mode".
+ * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger} Other names for DND (Do
+ * Not Disturb) include "Zen" and "Priority mode".
*/
enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
- @UiEvent(doc = "User selected an option on the DND dialog")
- QS_DND_CONDITION_SELECT(420),
-
+ @UiEvent(doc = "User selected an option on the DND dialog") QS_DND_CONDITION_SELECT(420),
@UiEvent(doc = "User increased countdown duration of DND from the DND dialog")
QS_DND_TIME_UP(422),
-
@UiEvent(doc = "User decreased countdown duration of DND from the DND dialog")
QS_DND_TIME_DOWN(423),
-
@UiEvent(doc = "User enabled DND from the QS DND dialog to last until manually turned off")
QS_DND_DIALOG_ENABLE_FOREVER(946),
-
@UiEvent(doc = "User enabled DND from the QS DND dialog to last until the next alarm goes off")
QS_DND_DIALOG_ENABLE_UNTIL_ALARM(947),
-
@UiEvent(doc = "User enabled DND from the QS DND dialog to last until countdown is done")
QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN(948);
@@ -121,29 +100,17 @@ enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
}
enum class QSUserSwitcherEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The current user has been switched in the detail panel")
- QS_USER_SWITCH(424),
-
- @UiEvent(doc = "User switcher QS dialog open")
- QS_USER_DETAIL_OPEN(425),
-
- @UiEvent(doc = "User switcher QS dialog closed")
- QS_USER_DETAIL_CLOSE(426),
-
- @UiEvent(doc = "User switcher QS dialog more settings pressed")
- QS_USER_MORE_SETTINGS(427),
-
- @UiEvent(doc = "The user has added a guest in the detail panel")
- QS_USER_GUEST_ADD(754),
-
+ @UiEvent(doc = "The current user has been switched in the detail panel") QS_USER_SWITCH(424),
+ @UiEvent(doc = "User switcher QS dialog open") QS_USER_DETAIL_OPEN(425),
+ @UiEvent(doc = "User switcher QS dialog closed") QS_USER_DETAIL_CLOSE(426),
+ @UiEvent(doc = "User switcher QS dialog more settings pressed") QS_USER_MORE_SETTINGS(427),
+ @UiEvent(doc = "The user has added a guest in the detail panel") QS_USER_GUEST_ADD(754),
@UiEvent(doc = "The user selected 'Start over' after switching to the existing Guest user")
QS_USER_GUEST_WIPE(755),
-
@UiEvent(doc = "The user selected 'Yes, continue' after switching to the existing Guest user")
QS_USER_GUEST_CONTINUE(756),
-
@UiEvent(doc = "The user has pressed 'Remove guest' in the detail panel")
QS_USER_GUEST_REMOVE(757);
override fun getId() = _id
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 6ad8bae05d7a..5930a24e01a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -306,6 +306,7 @@ constructor(
sceneState,
viewModel.containerViewModel.editModeViewModel.isEditing,
snapshotFlow { viewModel.expansionState }.map { it.progress },
+ snapshotFlow { viewModel.isQSExpandingOrCollapsing },
)
}
@@ -537,6 +538,10 @@ constructor(
return qqsVisible.value
}
+ override fun setQSExpandingOrCollapsing(isQSExpandingOrCollapsing: Boolean) {
+ viewModel.isQSExpandingOrCollapsing = isQSExpandingOrCollapsing
+ }
+
private fun setListenerCollections() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -877,6 +882,7 @@ private suspend fun synchronizeQsState(
state: MutableSceneTransitionLayoutState,
editMode: Flow<Boolean>,
expansion: Flow<Float>,
+ isQSExpandingOrCollapsing: Flow<Boolean>,
) {
coroutineScope {
val animationScope = this
@@ -888,31 +894,46 @@ private suspend fun synchronizeQsState(
currentTransition = null
}
- editMode.combine(expansion, ::Pair).collectLatest { (editMode, progress) ->
+ var lastValidProgress = 0f
+ combine(editMode, expansion, isQSExpandingOrCollapsing, ::Triple).collectLatest {
+ (editMode, progress, isQSExpandingOrCollapsing) ->
if (editMode && state.currentScene != SceneKeys.EditMode) {
state.setTargetScene(SceneKeys.EditMode, animationScope)?.second?.join()
} else if (!editMode && state.currentScene == SceneKeys.EditMode) {
state.setTargetScene(SceneKeys.QuickSettings, animationScope)?.second?.join()
}
+
if (!editMode) {
- when (progress) {
- 0f -> snapTo(QuickQuickSettings)
- 1f -> snapTo(QuickSettings)
- else -> {
- val transition = currentTransition
- if (transition != null) {
- transition.progress = progress
- return@collectLatest
- }
+ if (!isQSExpandingOrCollapsing) {
+ if (progress == 0f) {
+ snapTo(QuickQuickSettings)
+ return@collectLatest
+ }
- val newTransition =
- ExpansionTransition(progress).also { currentTransition = it }
- state.startTransitionImmediately(
- animationScope = animationScope,
- transition = newTransition,
- )
+ if (progress == 1f) {
+ snapTo(QuickSettings)
+ return@collectLatest
}
}
+
+ var progress = progress
+ if (progress >= 0f || progress <= 1f) {
+ lastValidProgress = progress
+ } else {
+ progress = lastValidProgress
+ }
+
+ val transition = currentTransition
+ if (transition != null) {
+ transition.progress = progress
+ return@collectLatest
+ }
+
+ val newTransition = ExpansionTransition(progress).also { currentTransition = it }
+ state.startTransitionImmediately(
+ animationScope = animationScope,
+ transition = newTransition,
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index ff84479ebcad..b829bbce2f18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -306,6 +306,8 @@ constructor(
val animateTilesExpansion: Boolean
get() = inFirstPage && !mediaSuddenlyAppearingInLandscape
+ var isQSExpandingOrCollapsing by mutableStateOf(false)
+
private val inFirstPage: Boolean
get() = inFirstPageViewModel.inFirstPage
@@ -539,6 +541,7 @@ constructor(
println("proposedTranslation", proposedTranslation)
println("expansionState", expansionState)
println("forceQS", forceQs)
+ println("isShadeExpandingOrCollapsing", isQSExpandingOrCollapsing)
printSection("Derived values") {
println("headerTranslation", headerTranslation)
println("translationScaleY", translationScaleY)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 482cd4014acf..3f279b0f7a74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -16,15 +16,18 @@
package com.android.systemui.qs.panels.domain.interactor
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.QSEditEvent
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.LargeTileSpanRepository
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.metricSpec
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -40,6 +43,7 @@ constructor(
private val repo: DefaultLargeTilesRepository,
private val currentTilesInteractor: CurrentTilesInteractor,
private val preferencesInteractor: QSPreferencesInteractor,
+ private val uiEventLogger: UiEventLogger,
largeTilesSpanRepo: LargeTileSpanRepository,
@PanelsLog private val logBuffer: LogBuffer,
@Application private val applicationScope: CoroutineScope,
@@ -70,8 +74,18 @@ constructor(
val isIcon = !largeTilesSpecs.value.contains(spec)
if (toIcon && !isIcon) {
preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
+ uiEventLogger.log(
+ /* event= */ QSEditEvent.QS_EDIT_RESIZE_SMALL,
+ /* uid= */ 0,
+ /* packageName= */ spec.metricSpec,
+ )
} else if (!toIcon && isIcon) {
preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
+ uiEventLogger.log(
+ /* event= */ QSEditEvent.QS_EDIT_RESIZE_LARGE,
+ /* uid= */ 0,
+ /* packageName= */ spec.metricSpec,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/DeviceStateAutoRotateModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/DeviceStateAutoRotateModule.kt
new file mode 100644
index 000000000000..628280210236
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/rotationlock/DeviceStateAutoRotateModule.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.rotationlock
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController
+import com.android.window.flags.Flags
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+import javax.inject.Provider
+import javax.inject.Qualifier
+
+@Module
+class DeviceStateAutoRotateModule {
+ /** Qualifier for dependencies to be bound with [DeviceStateAutoRotateModule]. */
+ @Qualifier
+ @MustBeDocumented
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class BoundsDeviceStateAutoRotateModule
+
+ /**
+ * Provides an instance of [DeviceStateRotationLockSettingController].
+ *
+ * @param controllerProvider The provider for [DeviceStateRotationLockSettingController].
+ * @return An [Optional] containing the [DeviceStateRotationLockSettingController] instance if
+ * the `Flags.enableDeviceStateAutoRotateSettingRefactor()` flag is disabled, or an empty
+ * [Optional] otherwise.
+ */
+ @Provides
+ @BoundsDeviceStateAutoRotateModule
+ @SysUISingleton
+ fun provideDeviceStateRotationLockSettingController(
+ controllerProvider: Provider<DeviceStateRotationLockSettingController>
+ ): Optional<DeviceStateRotationLockSettingController> =
+ if (Flags.enableDeviceStateAutoRotateSettingRefactor()) {
+ Optional.empty()
+ } else {
+ Optional.of(controllerProvider.get())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index d05837261b89..34b3324f81da 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -105,15 +105,14 @@ import com.android.systemui.util.kotlin.JavaAdapter;
import dalvik.annotation.optimization.NeverCompile;
-import dagger.Lazy;
-
-import kotlin.Unit;
-
import java.io.PrintWriter;
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
+import kotlin.Unit;
+
/** Handles QuickSettings touch handling, expansion and animation state. */
@SysUISingleton
public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable {
@@ -2366,8 +2365,16 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
return;
}
if (startTracing) {
+ if (mQs != null) {
+ mQs.setQSExpandingOrCollapsing(true);
+ }
+
monitor.begin(mPanelView, Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
} else {
+ if (mQs != null) {
+ mQs.setQSExpandingOrCollapsing(false);
+ }
+
if (wasCancelled) {
monitor.cancel(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt
index 186bfcbbc8e2..a4de1d675a15 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt
@@ -17,11 +17,19 @@
package com.android.systemui.shade.domain.interactor
import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
import android.provider.AlarmClock
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shade.data.repository.ShadeHeaderClockRepository
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.time.SystemClock
+import java.util.Date
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
@SysUISingleton
class ShadeHeaderClockInteractor
@@ -29,7 +37,20 @@ class ShadeHeaderClockInteractor
constructor(
private val repository: ShadeHeaderClockRepository,
private val activityStarter: ActivityStarter,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val systemClock: SystemClock,
) {
+ /** [Flow] that emits `Unit` whenever the timezone or locale has changed. */
+ val onTimezoneOrLocaleChanged: Flow<Unit> =
+ broadcastFlowForActions(Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_LOCALE_CHANGED)
+ .emitOnStart()
+
+ /** [Flow] that emits the current `Date` every minute, or when the system time has changed. */
+ val currentTime: Flow<Date> =
+ broadcastFlowForActions(Intent.ACTION_TIME_TICK, Intent.ACTION_TIME_CHANGED)
+ .emitOnStart()
+ .map { Date(systemClock.currentTimeMillis()) }
+
/** Launch the clock activity. */
fun launchClockActivity() {
val nextAlarmIntent = repository.nextAlarmIntent
@@ -38,8 +59,22 @@ constructor(
} else {
activityStarter.postStartActivityDismissingKeyguard(
Intent(AlarmClock.ACTION_SHOW_ALARMS),
- 0
+ 0,
)
}
}
+
+ /**
+ * Returns a `Flow` that, when collected, emits `Unit` whenever a broadcast matching one of the
+ * given [actionsToFilter] is received.
+ */
+ private fun broadcastFlowForActions(
+ vararg actionsToFilter: String,
+ user: UserHandle = UserHandle.SYSTEM,
+ ): Flow<Unit> {
+ return broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter().apply { actionsToFilter.forEach(::addAction) },
+ user = user,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 8c38d2e7550c..20b44d73e097 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -18,16 +18,15 @@ package com.android.systemui.shade.ui.viewmodel
import android.content.Context
import android.content.Intent
-import android.content.IntentFilter
import android.icu.text.DateFormat
import android.icu.text.DisplayContext
-import android.os.UserHandle
import android.provider.Settings
import android.view.ViewGroup
+import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.getValue
+import androidx.compose.ui.graphics.Color
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.battery.BatteryMeterViewController
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.plugins.ActivityStarter
@@ -42,7 +41,6 @@ import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
@@ -50,18 +48,18 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import java.util.Date
import java.util.Locale
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.mapLatest
/** Models UI state for the shade header. */
+@OptIn(ExperimentalCoroutinesApi::class)
class ShadeHeaderViewModel
@AssistedInject
constructor(
@@ -70,16 +68,15 @@ constructor(
private val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
private val shadeModeInteractor: ShadeModeInteractor,
- private val mobileIconsInteractor: MobileIconsInteractor,
+ mobileIconsInteractor: MobileIconsInteractor,
val mobileIconsViewModel: MobileIconsViewModel,
private val privacyChipInteractor: PrivacyChipInteractor,
private val clockInteractor: ShadeHeaderClockInteractor,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
val statusBarIconController: StatusBarIconController,
- val notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder,
- private val broadcastDispatcher: BroadcastDispatcher,
) : ExclusiveActivatable() {
+
private val hydrator = Hydrator("ShadeHeaderViewModel.hydrator")
val createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager =
@@ -127,9 +124,16 @@ constructor(
/** True if there is exactly one mobile connection. */
val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
- private val _mobileSubIds = MutableStateFlow(emptyList<Int>())
/** The list of subscription Ids for current mobile connections. */
- val mobileSubIds: StateFlow<List<Int>> = _mobileSubIds.asStateFlow()
+ val mobileSubIds: List<Int> by
+ hydrator.hydratedStateOf(
+ traceName = "mobileSubIds",
+ initialValue = emptyList(),
+ source =
+ mobileIconsInteractor.filteredSubscriptions.map { list ->
+ list.map { it.subscriptionId }
+ },
+ )
/** The list of PrivacyItems to be displayed by the privacy chip. */
val privacyItems: StateFlow<List<PrivacyItem>> = privacyChipInteractor.privacyItems
@@ -150,45 +154,34 @@ constructor(
private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
- private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern))
- private val shorterDateFormat = MutableStateFlow(getFormatFromPattern(shorterPattern))
- private val _shorterDateText: MutableStateFlow<String> = MutableStateFlow("")
- val shorterDateText: StateFlow<String> = _shorterDateText.asStateFlow()
+ private val longerDateFormat: Flow<DateFormat> =
+ clockInteractor.onTimezoneOrLocaleChanged.mapLatest { getFormatFromPattern(longerPattern) }
+ private val shorterDateFormat: Flow<DateFormat> =
+ clockInteractor.onTimezoneOrLocaleChanged.mapLatest { getFormatFromPattern(shorterPattern) }
- private val _longerDateText: MutableStateFlow<String> = MutableStateFlow("")
- val longerDateText: StateFlow<String> = _longerDateText.asStateFlow()
+ val longerDateText: String by
+ hydrator.hydratedStateOf(
+ traceName = "longerDateText",
+ initialValue = "",
+ source =
+ combine(longerDateFormat, clockInteractor.currentTime) { format, time ->
+ format.format(time)
+ },
+ )
+
+ val shorterDateText: String by
+ hydrator.hydratedStateOf(
+ traceName = "shorterDateText",
+ initialValue = "",
+ source =
+ combine(shorterDateFormat, clockInteractor.currentTime) { format, time ->
+ format.format(time)
+ },
+ )
override suspend fun onActivated(): Nothing {
coroutineScope {
- launch {
- broadcastDispatcher
- .broadcastFlow(
- filter =
- IntentFilter().apply {
- addAction(Intent.ACTION_TIME_TICK)
- addAction(Intent.ACTION_TIME_CHANGED)
- addAction(Intent.ACTION_TIMEZONE_CHANGED)
- addAction(Intent.ACTION_LOCALE_CHANGED)
- },
- user = UserHandle.SYSTEM,
- map = { intent, _ ->
- intent.action == Intent.ACTION_TIMEZONE_CHANGED ||
- intent.action == Intent.ACTION_LOCALE_CHANGED
- },
- )
- .onEach { invalidateFormats -> updateDateTexts(invalidateFormats) }
- .launchIn(this)
- }
-
- launch { updateDateTexts(false) }
-
- launch {
- mobileIconsInteractor.filteredSubscriptions
- .map { list -> list.map { it.subscriptionId } }
- .collect { _mobileSubIds.value = it }
- }
-
launch { hydrator.activate() }
awaitCancellation()
@@ -253,33 +246,34 @@ constructor(
/** Represents the background highlight of a header icons chip. */
sealed interface HeaderChipHighlight {
- data object None : HeaderChipHighlight
- data object Weak : HeaderChipHighlight
+ fun backgroundColor(colorScheme: ColorScheme): Color
- data object Strong : HeaderChipHighlight
- }
+ fun foregroundColor(colorScheme: ColorScheme): Color
+
+ data object None : HeaderChipHighlight {
+ override fun backgroundColor(colorScheme: ColorScheme): Color = Color.Unspecified
+
+ override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary
+ }
+
+ data object Weak : HeaderChipHighlight {
+ override fun backgroundColor(colorScheme: ColorScheme): Color =
+ colorScheme.primary.copy(alpha = 0.1f)
- private fun updateDateTexts(invalidateFormats: Boolean) {
- if (invalidateFormats) {
- longerDateFormat.value = getFormatFromPattern(longerPattern)
- shorterDateFormat.value = getFormatFromPattern(shorterPattern)
+ override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary
}
- val currentTime = Date()
+ data object Strong : HeaderChipHighlight {
+ override fun backgroundColor(colorScheme: ColorScheme): Color = colorScheme.secondary
- _longerDateText.value = longerDateFormat.value.format(currentTime)
- _shorterDateText.value = shorterDateFormat.value.format(currentTime)
+ override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.onSecondary
+ }
}
private fun getFormatFromPattern(pattern: String?): DateFormat {
- val l = Locale.getDefault()
- val format = DateFormat.getInstanceForSkeleton(pattern, l)
- // The use of CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE instead of
- // CAPITALIZATION_FOR_STANDALONE is to address
- // https://unicode-org.atlassian.net/browse/ICU-21631
- // TODO(b/229287642): Switch back to CAPITALIZATION_FOR_STANDALONE
- format.setContext(DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE)
+ val format = DateFormat.getInstanceForSkeleton(pattern, Locale.getDefault())
+ format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
return format
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 97de61969ffb..7dc2ae71b63e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -58,7 +58,6 @@ import android.view.KeyEvent;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
-import android.view.accessibility.Flags;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -987,13 +986,7 @@ public class CommandQueue extends IStatusBar.Stub implements
@Override
public void addQsTile(ComponentName tile) {
- if (Flags.a11yQsShortcut()) {
- addQsTileToFrontOrEnd(tile, false);
- } else {
- synchronized (mLock) {
- mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
- }
- }
+ addQsTileToFrontOrEnd(tile, false);
}
/**
@@ -1003,13 +996,11 @@ public class CommandQueue extends IStatusBar.Stub implements
*/
@Override
public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
- if (Flags.a11yQsShortcut()) {
- synchronized (mLock) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = tile;
- args.arg2 = end;
- mHandler.obtainMessage(MSG_ADD_QS_TILE, args).sendToTarget();
- }
+ synchronized (mLock) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = tile;
+ args.arg2 = end;
+ mHandler.obtainMessage(MSG_ADD_QS_TILE, args).sendToTarget();
}
}
@@ -1692,18 +1683,12 @@ public class CommandQueue extends IStatusBar.Stub implements
}
break;
case MSG_ADD_QS_TILE: {
- if (Flags.a11yQsShortcut()) {
- SomeArgs someArgs = (SomeArgs) msg.obj;
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).addQsTileToFrontOrEnd(
- (ComponentName) someArgs.arg1, (boolean) someArgs.arg2);
- }
- someArgs.recycle();
- } else {
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).addQsTile((ComponentName) msg.obj);
- }
+ SomeArgs someArgs = (SomeArgs) msg.obj;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).addQsTileToFrontOrEnd(
+ (ComponentName) someArgs.arg1, (boolean) someArgs.arg2);
}
+ someArgs.recycle();
break;
}
case MSG_REMOVE_QS_TILE:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 79a872edd2c5..bfd512fa6a2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -607,13 +607,6 @@ public final class KeyboardShortcutListSearch {
context.getString(R.string.group_system_lock_screen),
Arrays.asList(
Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))),
- /* Pull up Notes app for quick memo: Meta + Ctrl + N */
- new ShortcutKeyGroupMultiMappingInfo(
- context.getString(R.string.group_system_quick_memo),
- Arrays.asList(
- Pair.create(
- KeyEvent.KEYCODE_N,
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))),
/* Access system settings: Meta + I */
new ShortcutKeyGroupMultiMappingInfo(
context.getString(R.string.group_system_access_system_settings),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index c1b8d9d123b9..6ebe02469f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static android.app.Flags.notificationsRedesignTemplates;
+
import android.app.Flags;
import android.app.Notification;
import android.graphics.drawable.Drawable;
@@ -427,7 +429,8 @@ public class NotificationGroupingUtil {
@Override
public void apply(View parent, View view, boolean apply, boolean reset) {
- if (reset && parent instanceof ConversationLayout) {
+ if (!notificationsRedesignTemplates()
+ && reset && parent instanceof ConversationLayout) {
ConversationLayout layout = (ConversationLayout) parent;
apply = layout.shouldHideAppName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f06565f1b6d2..32da6fff6bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -24,7 +24,7 @@ import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_
import static android.os.Flags.allowPrivateProfile;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
-import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY;
+import static android.provider.Settings.Secure.OTP_NOTIFICATION_REDACTION_LOCK_TIME;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI;
@@ -124,10 +124,10 @@ public class NotificationLockscreenUserManagerImpl implements
private static final Uri REDACT_OTP_ON_WIFI =
Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI);
- private static final Uri REDACT_OTP_IMMEDIATELY =
- Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_IMMEDIATELY);
+ private static final Uri OTP_REDACTION_LOCK_TIME =
+ Settings.Secure.getUriFor(OTP_NOTIFICATION_REDACTION_LOCK_TIME);
- private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
+ private static final long DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
TimeUnit.MINUTES.toMillis(10);
private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
@@ -316,7 +316,8 @@ public class NotificationLockscreenUserManagerImpl implements
protected final AtomicBoolean mConnectedToWifi = new AtomicBoolean(false);
protected final AtomicBoolean mRedactOtpOnWifi = new AtomicBoolean(true);
- protected final AtomicBoolean mRedactOtpImmediately = new AtomicBoolean(false);
+ protected final AtomicLong mOtpRedactionRequiredLockTimeMs =
+ new AtomicLong(DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS);
protected int mCurrentUserId = 0;
@@ -375,7 +376,7 @@ public class NotificationLockscreenUserManagerImpl implements
mLockScreenUris.add(SHOW_LOCKSCREEN);
mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
mLockScreenUris.add(REDACT_OTP_ON_WIFI);
- mLockScreenUris.add(REDACT_OTP_IMMEDIATELY);
+ mLockScreenUris.add(OTP_REDACTION_LOCK_TIME);
dumpManager.registerDumpable(this);
@@ -447,8 +448,8 @@ public class NotificationLockscreenUserManagerImpl implements
changed |= updateUserShowPrivateSettings(user.getIdentifier());
} else if (REDACT_OTP_ON_WIFI.equals(uri)) {
changed |= updateRedactOtpOnWifiSetting();
- } else if (REDACT_OTP_IMMEDIATELY.equals(uri)) {
- changed |= updateRedactOtpImmediatelySetting();
+ } else if (OTP_REDACTION_LOCK_TIME.equals(uri)) {
+ changed |= updateOtpLockTimeSetting();
}
}
@@ -487,7 +488,7 @@ public class NotificationLockscreenUserManagerImpl implements
mLockscreenSettingsObserver
);
mSecureSettings.registerContentObserverAsync(
- REDACT_OTP_IMMEDIATELY,
+ OTP_REDACTION_LOCK_TIME,
mLockscreenSettingsObserver
);
@@ -638,13 +639,13 @@ public class NotificationLockscreenUserManagerImpl implements
}
@WorkerThread
- private boolean updateRedactOtpImmediatelySetting() {
- boolean originalValue = mRedactOtpImmediately.get();
- boolean newValue = mSecureSettings.getIntForUser(
- REDACT_OTP_NOTIFICATION_IMMEDIATELY,
- 0,
- Process.myUserHandle().getIdentifier()) != 0;
- mRedactOtpImmediately.set(newValue);
+ private boolean updateOtpLockTimeSetting() {
+ long originalValue = mOtpRedactionRequiredLockTimeMs.get();
+ long newValue = mSecureSettings.getLongForUser(
+ OTP_NOTIFICATION_REDACTION_LOCK_TIME,
+ DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS,
+ Process.myUserHandle().getIdentifier());
+ mOtpRedactionRequiredLockTimeMs.set(newValue);
return originalValue != newValue;
}
@@ -832,14 +833,9 @@ public class NotificationLockscreenUserManagerImpl implements
return false;
}
- long latestTimeForRedaction;
- if (mRedactOtpImmediately.get()) {
- latestTimeForRedaction = mLastLockTime.get();
- } else {
- // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS
- // when this notification arrived, do not redact
- latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
- }
+ // If the lock screen was not already locked for at least mOtpRedactionRequiredLockTimeMs
+ // when this notification arrived, do not redact
+ long latestTimeForRedaction = mLastLockTime.get() + mOtpRedactionRequiredLockTimeMs.get();
if (ent.getSbn().getPostTime() < latestTimeForRedaction) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
index 2eae3eb4fc19..7548f6ff5bd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
@@ -22,9 +22,9 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -44,25 +44,17 @@ constructor(
@StatusBarChipsLog private val logger: LogBuffer,
) {
val ongoingCallState: StateFlow<OngoingCallModel> =
- (if (StatusBarChipsModernization.isEnabled)
- ongoingCallInteractor.ongoingCallState
- else
- repository.ongoingCallState)
+ (if (StatusBarChipsModernization.isEnabled) {
+ ongoingCallInteractor.ongoingCallState
+ } else {
+ repository.ongoingCallState
+ })
.onEach {
- logger.log(
- TAG,
- LogLevel.INFO,
- { str1 = it::class.simpleName },
- { "State: $str1" }
- )
+ logger.log(TAG, LogLevel.INFO, { str1 = it::class.simpleName }, { "State: $str1" })
}
- .stateIn(
- scope,
- SharingStarted.Lazily,
- OngoingCallModel.NoCall
- )
+ .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall)
companion object {
private val TAG = "OngoingCall".pad()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
index e3be95373698..402881d438dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
@@ -29,10 +29,10 @@ object NewStatusBarIcons {
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
- /** Is the refactor enabled */
+ /** Is the refactor enabled. Dependency on [StatusBarRootModernization] */
@JvmStatic
inline val isEnabled
- get() = Flags.newStatusBarIcons()
+ get() = Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index 0e3f103c152e..24ab6959b9e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -21,9 +21,14 @@ import static android.app.NotificationChannel.PROMOTIONS_ID;
import static android.app.NotificationChannel.RECS_ID;
import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
+import android.app.Notification;
+import android.content.Context;
+import android.os.Build;
+
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import java.util.List;
@@ -79,6 +84,43 @@ public class BundleEntry extends PipelineEntry {
public EntryAdapter getGroupRoot() {
return this;
}
+
+ @Override
+ public boolean isClearable() {
+ // TODO(b/394483200): check whether all of the children are clearable, when implemented
+ return true;
+ }
+
+ @Override
+ public int getTargetSdk() {
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+
+ @Override
+ public String getSummarization() {
+ return null;
+ }
+
+ @Override
+ public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) {
+ return Notification.COLOR_DEFAULT;
+ }
+
+ @Override
+ public boolean canPeek() {
+ return false;
+ }
+
+ @Override
+ public long getWhen() {
+ return 0;
+ }
+
+ @Override
+ public IconPack getIcons() {
+ // TODO(b/396446620): implement bundle icons
+ return null;
+ }
}
public static final List<BundleEntry> ROOT_BUNDLES = List.of(
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 4df81c97e21e..6431cacf2107 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
@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.notification.collection;
+import android.content.Context;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
@@ -59,9 +62,49 @@ public interface EntryAdapter {
EntryAdapter getGroupRoot();
/**
+ * @return whether the row can be removed with the 'Clear All' action
+ */
+ boolean isClearable();
+
+ /**
* Returns whether the entry is attached to the current shade list
*/
default boolean isAttached() {
return getParent() != null;
}
+
+ /**
+ * Returns the target sdk of the package that owns this entry.
+ */
+ int getTargetSdk();
+
+ /**
+ * Returns the summarization for this entry, if there is one
+ */
+ @Nullable String getSummarization();
+
+ /**
+ * Performs any steps needed to set or reset data before an inflation or reinflation.
+ */
+ default void prepareForInflation() {}
+
+ /**
+ * Gets a color that would have sufficient contrast on the given background color.
+ */
+ int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor);
+
+ /**
+ * Whether this entry can peek on screen as a heads up view
+ */
+ boolean canPeek();
+
+ /**
+ * Returns the visible 'time', in milliseconds, of the entry
+ */
+ long getWhen();
+
+ /**
+ * Retrieves the pack of icons associated with this entry
+ */
+ IconPack getIcons();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 90f9525c7683..698a56363465 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -78,6 +78,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.PriorityBucket;
import com.android.systemui.util.ListenerSet;
@@ -309,6 +310,47 @@ public final class NotificationEntry extends ListEntry {
}
return null;
}
+
+ @Override
+ public boolean isClearable() {
+ return NotificationEntry.this.isClearable();
+ }
+
+ @Override
+ public int getTargetSdk() {
+ return NotificationEntry.this.targetSdk;
+ }
+
+ @Override
+ public String getSummarization() {
+ return getRanking().getSummarization();
+ }
+
+ @Override
+ public void prepareForInflation() {
+ getSbn().clearPackageContext();
+ }
+
+ @Override
+ public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) {
+ return NotificationEntry.this.getContrastedColor(
+ context, isLowPriority, backgroundColor);
+ }
+
+ @Override
+ public boolean canPeek() {
+ return isStickyAndNotDemoted();
+ }
+
+ @Override
+ public long getWhen() {
+ return getSbn().getNotification().getWhen();
+ }
+
+ @Override
+ public IconPack getIcons() {
+ return NotificationEntry.this.getIcons();
+ }
}
public EntryAdapter getEntryAdapter() {
@@ -580,6 +622,7 @@ public final class NotificationEntry extends ListEntry {
}
public boolean hasFinishedInitialization() {
+ NotificationBundleUi.assertInLegacyMode();
return initializationTime != -1
&& SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
}
@@ -663,10 +706,12 @@ public final class NotificationEntry extends ListEntry {
}
public void resetInitializationTime() {
+ NotificationBundleUi.assertInLegacyMode();
initializationTime = -1;
}
public void setInitializationTime(long time) {
+ NotificationBundleUi.assertInLegacyMode();
if (initializationTime == -1) {
initializationTime = time;
}
@@ -683,9 +728,13 @@ public final class NotificationEntry extends ListEntry {
* @return {@code true} if we are a media notification
*/
public boolean isMediaNotification() {
- if (row == null) return false;
+ if (NotificationBundleUi.isEnabled()) {
+ return getSbn().getNotification().isMediaNotification();
+ } else {
+ if (row == null) return false;
- return row.isMediaRow();
+ return row.isMediaRow();
+ }
}
public boolean containsCustomViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 9c1d0735a65b..ac11c15e8bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -67,6 +67,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.util.Assert;
import com.android.systemui.util.NamedListenerSet;
@@ -1282,7 +1283,13 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
entry.getAttachState().setExcludingFilter(filter);
if (filter != null) {
// notification is removed from the list, so we reset its initialization time
- entry.resetInitializationTime();
+ if (NotificationBundleUi.isEnabled()) {
+ if (entry.getRow() != null) {
+ entry.getRow().resetInitializationTime();
+ }
+ } else {
+ entry.resetInitializationTime();
+ }
}
return filter != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index dba8a9a6ddec..867db8ec8235 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -163,6 +163,7 @@ constructor(
if (entry in nextMap) nextMap.remove(entry)
if (entry in nextList) nextList.remove(entry)
+ outcome = "add next"
addToNext(entry, runnable)
// Shorten headsUpEntryShowing display time
@@ -174,7 +175,7 @@ constructor(
headsUpEntryShowing!!.updateEntry(
/* updatePostTime= */ false,
/* updateEarliestRemovalTime= */ false,
- /* reason= */ "avalanche duration update",
+ /* reason= */ "shorten duration of previously-last HUN",
)
}
}
@@ -269,8 +270,10 @@ constructor(
}
nextList.sort()
val entryList = showingList + nextList
+ val thisKey = getKey(entry)
if (entryList.isEmpty()) {
- log { "No avalanche HUNs, use default ms: $autoDismissMs" }
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey, autoDismissMs, "No avalanche HUNs, use default", nextKey = "")
return autoDismissMs
}
// entryList.indexOf(entry) returns -1 even when the entry is in entryList
@@ -281,27 +284,29 @@ constructor(
}
}
if (thisEntryIndex == -1) {
- log { "Untracked entry, use default ms: $autoDismissMs" }
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey, autoDismissMs, "Untracked entry, use default", nextKey = "")
return autoDismissMs
}
val nextEntryIndex = thisEntryIndex + 1
-
- // If last entry, use default duration
if (nextEntryIndex >= entryList.size) {
- log { "Last entry, use default ms: $autoDismissMs" }
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey, autoDismissMs, "Last entry, use default", nextKey = "")
return autoDismissMs
}
val nextEntry = entryList[nextEntryIndex]
+ val nextKey = getKey(nextEntry)
if (nextEntry.compareNonTimeFields(entry) == -1) {
- // Next entry is higher priority
- log { "Next entry is higher priority: 500ms" }
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey, 500, "LOWER priority than next: ", nextKey)
return 500
} else if (nextEntry.compareNonTimeFields(entry) == 0) {
- // Next entry is same priority
- log { "Next entry is same priority: 1000ms" }
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey, 1000, "SAME priority as next: ", nextKey)
return 1000
} else {
- log { "Next entry is lower priority, use default ms: $autoDismissMs" }
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey, autoDismissMs, "HIGHER priority than next: ", nextKey)
return autoDismissMs
}
}
@@ -355,25 +360,28 @@ constructor(
}
private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
- log { "SHOW: " + getKey(entry) }
-
+ headsUpManagerLogger.logAvalancheStage("show", getKey(entry))
uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN)
headsUpEntryShowing = entry
- runnableList.forEach {
- if (it in debugRunnableLabelMap) {
- log { "RUNNABLE: ${debugRunnableLabelMap[it]}" }
+ runnableList.forEach { runnable ->
+ if (debug) {
+ debugRunnableLabelMap[runnable]?.let { label ->
+ headsUpManagerLogger.logAvalancheStage("run", label)
+ // Remove label after logging to avoid memory leak
+ debugRunnableLabelMap.remove(runnable)
+ }
}
- it.run()
+ runnable.run()
}
}
private fun showNext() {
- log { "SHOW NEXT" }
+ headsUpManagerLogger.logAvalancheStage("show next", key = "")
headsUpEntryShowing = null
if (nextList.isEmpty()) {
- log { "NO MORE TO SHOW" }
+ headsUpManagerLogger.logAvalancheStage("no more", key = "")
previousHunKey = ""
return
}
@@ -395,11 +403,7 @@ constructor(
debugRunnableLabelMap.remove(r)
}
}
- val queue = ArrayList<String>()
- for (entry in listToDrop) {
- queue.add("[${getKey(entry)}]")
- }
- val dropList = java.lang.String.join("\n", queue)
+ val dropList = listToDrop.joinToString("\n ") { getKey(it) }
headsUpManagerLogger.logDroppedHuns(dropList)
}
clearNext()
@@ -424,38 +428,24 @@ constructor(
// Methods below are for logging only ==========================================================
- private inline fun log(s: () -> String) {
- if (debug) {
- Log.d(tag, s())
- }
- }
-
private fun getStateStr(): String {
- return "\navalanche state:" +
- "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
- "\n\tprevious: [$previousHunKey]" +
- "\n\tnext list: $nextListStr" +
- "\n\tnext map: $nextMapStr" +
- "\nBHUM.mHeadsUpEntryMap: " +
- baseEntryMapStr()
+ return "\n[AC state]" +
+ "\nshow: ${getKey(headsUpEntryShowing)}" +
+ "\nprevious: $previousHunKey" +
+ "\n$nextStr" +
+ "\n[HeadsUpManagerImpl.mHeadsUpEntryMap] " + baseEntryMapStr() + "\n"
}
- private val nextListStr: String
+ private val nextStr: String
get() {
- val queue = ArrayList<String>()
- for (entry in nextList) {
- queue.add("[${getKey(entry)}]")
+ val nextListStr = nextList.joinToString("\n ") { getKey(it) }
+ if (nextList.toSet() == nextMap.keys.toSet()) {
+ return "next (${nextList.size}):\n $nextListStr"
}
- return java.lang.String.join("\n", queue)
- }
-
- private val nextMapStr: String
- get() {
- val queue = ArrayList<String>()
- for (entry in nextMap.keys) {
- queue.add("[${getKey(entry)}]")
- }
- return java.lang.String.join("\n", queue)
+ // This should never happen
+ val nextMapStr = nextMap.keys.joinToString("\n ") { getKey(it) }
+ return "next list (${nextList.size}):\n $nextListStr" +
+ "\nnext map (${nextMap.size}):\n $nextMapStr"
}
fun getKey(entry: HeadsUpEntry?): String {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 7d74a496853f..87b9bf87c680 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -856,11 +856,11 @@ public class HeadsUpManagerImpl
private String getEntryMapStr() {
if (mHeadsUpEntryMap.isEmpty()) {
- return "EMPTY";
+ return "";
}
StringBuilder entryMapStr = new StringBuilder();
for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
- entryMapStr.append("\n\t").append(
+ entryMapStr.append("\n ").append(
entry.mEntry == null ? "null" : entry.mEntry.getKey());
}
return entryMapStr.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
index 65fb9ca558d7..388d357b3b15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
@@ -71,7 +71,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str3 = outcome
bool1 = isEnabled
},
- { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" },
+ { "$str1\n=> AC[enabled:$bool1] update: $str2\n=> $str3" },
)
}
@@ -90,7 +90,33 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str3 = outcome
bool1 = isEnabled
},
- { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" },
+ { "$str1\n=> AC[enabled:$bool1] delete: $str2\n=> $str3" },
+ )
+ }
+
+ fun logAvalancheStage(stage: String, key: String) {
+ buffer.log(
+ TAG,
+ INFO,
+ {
+ str1 = stage
+ str2 = key
+ },
+ { "[AC] $str1 $str2" },
+ )
+ }
+
+ fun logAvalancheDuration(thisKey: String, duration: Int, reason: String, nextKey: String) {
+ buffer.log(
+ TAG,
+ INFO,
+ {
+ str1 = thisKey
+ int1 = duration
+ str2 = reason
+ str3 = nextKey
+ },
+ { "[AC] $str1 | $int1 ms | $str2 $str3" },
)
}
@@ -325,7 +351,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
}
fun logDroppedHuns(entryList: String) {
- buffer.log(TAG, VERBOSE, { str1 = entryList }, { "[AC] Drop HUNs: $str1" })
+ buffer.log(TAG, VERBOSE, { str1 = entryList }, { "[AC] dropped:\n $str1" })
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 9aa5a2e32ada..7959e99f3189 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.promoted
import android.app.Flags
+import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
import android.graphics.PorterDuff
import android.view.LayoutInflater
@@ -166,7 +167,10 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private val closeButton: View? = root.findViewById(R.id.close_button)
private val conversationIconBadge: View? = root.findViewById(R.id.conversation_icon_badge)
private val conversationIcon: CachingIconView? = root.findViewById(R.id.conversation_icon)
- private val conversationText: TextView? = root.findViewById(R.id.conversation_text)
+ private val conversationText: TextView? =
+ root.findViewById(
+ if (notificationsRedesignTemplates()) R.id.title else R.id.conversation_text
+ )
private val expandButton: NotificationExpandButton? = root.findViewById(R.id.expand_button)
private val headerText: TextView? = root.findViewById(R.id.header_text)
private val headerTextDivider: View? = root.findViewById(R.id.header_text_divider)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index b4092cd785bf..b0773276a3e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.Flags.notificationRowTransparency;
import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM;
import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP;
@@ -38,6 +39,7 @@ import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.systemui.Gefingerpoken;
+import com.android.systemui.common.shared.colors.SurfaceEffectColors;
import com.android.systemui.res.R;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.NotificationShelf;
@@ -122,8 +124,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private void updateColors() {
- mNormalColor = mContext.getColor(
- com.android.internal.R.color.materialColorSurfaceContainerHigh);
+ if (notificationRowTransparency()) {
+ mNormalColor = SurfaceEffectColors.surfaceEffect1(getResources());
+ } else {
+ mNormalColor = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainerHigh);
+ }
mTintedRippleColor = mContext.getColor(
R.color.notification_ripple_tinted_color);
mNormalRippleColor = mContext.getColor(
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 66a0fb4ee4ab..6f7eade1c0f2 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
@@ -21,6 +21,7 @@ import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static com.android.systemui.Flags.notificationsPinnedHunInShade;
+import static com.android.systemui.Flags.notificationRowTransparency;
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;
@@ -45,6 +46,7 @@ import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -116,7 +118,6 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
@@ -168,6 +169,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
+ private static final long INITIALIZATION_DELAY = 400;
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
@@ -266,7 +268,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private NotificationContentView mPublicLayout;
private NotificationContentView mPrivateLayout;
private NotificationContentView[] mLayouts;
- private int mNotificationColor;
private ExpandableNotificationRowLogger mLogger;
private String mLoggingKey;
private NotificationGuts mGuts;
@@ -351,6 +352,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
private boolean mSaveSpaceOnLockscreen;
+ // indicates when this view was first attached to a window
+ // this value will reset when the view is completely removed from the shade (ie: filtered out)
+ private long initializationTime = -1;
+
/**
* It is added for unit testing purpose.
* Please do not use it for other purposes.
@@ -624,26 +629,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
- * Marks a content view as freeable, setting it so that future inflations do not reinflate
- * and ensuring that the view is freed when it is safe to remove.
- *
- * @param inflationFlag flag corresponding to the content view to be freed
- * @deprecated By default, {@link NotificationRowContentBinder#unbindContent} will tell the
- * view hierarchy to only free when the view is safe to remove so this method is no longer
- * needed. Will remove when all uses are gone.
- */
- @Deprecated
- public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
- RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
- params.markContentViewsFreeable(inflationFlag);
- mRowContentBindStage.requestRebind(mEntry, null /* callback */);
- }
-
- /**
* Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif
* or is in an allowList).
*/
public boolean getIsNonblockable() {
+ NotificationBundleUi.assertInLegacyMode();
if (mEntry == null) {
return true;
}
@@ -665,9 +655,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
l.onNotificationUpdated(mEntry);
}
mShowingPublicInitialized = false;
- updateNotificationColor();
if (mMenuRow != null) {
- mMenuRow.onNotificationUpdated(mEntry.getSbn());
+ mMenuRow.onNotificationUpdated();
mMenuRow.setAppName(mAppName);
}
if (mIsSummaryWithChildren) {
@@ -726,15 +715,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
- * Called when the notification's ranking was changed (but nothing else changed).
- */
- public void onNotificationRankingUpdated() {
- if (mMenuRow != null) {
- mMenuRow.onNotificationUpdated(mEntry.getSbn());
- }
- }
-
- /**
* Call when bubble state has changed and the button on the notification should be updated.
*/
public void updateBubbleButton() {
@@ -745,7 +725,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@VisibleForTesting
void updateShelfIconColor() {
- StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();
+ StatusBarIconView expandedIcon = getShelfIcon();
boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
ContrastColorUtil.getInstance(mContext));
@@ -766,8 +746,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (color != Notification.COLOR_INVALID) {
return color;
} else {
- return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
- getBackgroundColorWithoutTint());
+ if (NotificationBundleUi.isEnabled()) {
+ return mEntryAdapter.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
+ getBackgroundColorWithoutTint());
+ } else {
+ return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
+ getBackgroundColorWithoutTint());
+ }
}
}
@@ -869,15 +854,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean customView = contractedView != null
&& contractedView.getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
- boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
- boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
- boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S;
+ int targetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ if (NotificationBundleUi.isEnabled()) {
+ targetSdk = mEntryAdapter.getTargetSdk();
+ } else {
+ targetSdk = mEntry.targetSdk;
+ }
+
+ boolean beforeN = targetSdk < Build.VERSION_CODES.N;
+ boolean beforeP = targetSdk < Build.VERSION_CODES.P;
+ boolean beforeS = targetSdk < Build.VERSION_CODES.S;
int smallHeight;
boolean isCallLayout = contractedView instanceof CallLayout;
boolean isMessagingLayout = contractedView instanceof MessagingLayout
|| contractedView instanceof ConversationLayout;
+ String summarization = null;
+ if (NotificationBundleUi.isEnabled()) {
+ summarization = mEntryAdapter.getSummarization();
+ } else {
+ summarization = mEntry.getRanking().getSummarization();
+ }
+
if (customView && beforeS && !mIsSummaryWithChildren) {
if (beforeN) {
smallHeight = mMaxSmallHeightBeforeN;
@@ -890,7 +889,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
smallHeight = maxExpandedHeight;
} else if (NmSummarizationUiFlag.isEnabled()
&& isMessagingLayout
- && !TextUtils.isEmpty(mEntry.getRanking().getSummarization())) {
+ && !TextUtils.isEmpty(summarization)) {
smallHeight = mMaxSmallHeightWithSummarization;
} else {
smallHeight = mMaxSmallHeight;
@@ -1523,7 +1522,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public boolean hasFinishedInitialization() {
- return getEntry().hasFinishedInitialization();
+ if (NotificationBundleUi.isEnabled()) {
+ return initializationTime != -1
+ && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
+ } else {
+ return getEntry().hasFinishedInitialization();
+ }
+ }
+
+ public void resetInitializationTime() {
+ initializationTime = -1;
+ }
+
+ public void setInitializationTime(long time) {
+ if (initializationTime == -1) {
+ initializationTime = time;
+ }
}
/**
@@ -1538,7 +1552,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return null;
}
if (mMenuRow.getMenuView() == null) {
- mMenuRow.createMenu(this, mEntry.getSbn());
+ mMenuRow.createMenu(this);
mMenuRow.setAppName(mAppName);
FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
@@ -1580,7 +1594,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (oldMenu != null) {
int menuIndex = indexOfChild(oldMenu);
removeView(oldMenu);
- mMenuRow.createMenu(ExpandableNotificationRow.this, mEntry.getSbn());
+ mMenuRow.createMenu(ExpandableNotificationRow.this);
mMenuRow.setAppName(mAppName);
addView(mMenuRow.getMenuView(), menuIndex);
}
@@ -1588,12 +1602,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
l.reinflate();
l.reInflateViews();
}
- mEntry.getSbn().clearPackageContext();
+ if (NotificationBundleUi.isEnabled()) {
+ mEntryAdapter.prepareForInflation();
+ } else {
+ mEntry.getSbn().clearPackageContext();
+ }
// TODO: Move content inflation logic out of this call
RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
params.setNeedsReinflation(true);
- var rebindEndCallback = mRebindingTracker.trackRebinding(mEntry.getKey());
+ var rebindEndCallback = mRebindingTracker.trackRebinding(NotificationBundleUi.isEnabled()
+ ? mEntryAdapter.getKey() : mEntry.getKey());
mRowContentBindStage.requestRebind(mEntry, (e) -> rebindEndCallback.onFinished());
Trace.endSection();
}
@@ -1636,6 +1655,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (view != null) {
view.setBackgroundTintColor(color);
}
+ if (notificationRowTransparency()
+ && (mBackgroundNormal != null)
+ && (mEntry != null)) {
+ mBackgroundNormal.setBgIsColorized(
+ mEntry.getSbn().getNotification().isColorized());
+ }
}
public void closeRemoteInput() {
@@ -1651,20 +1676,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPrivateLayout.setSingleLineWidthIndention(indention);
}
- public int getNotificationColor() {
- return mNotificationColor;
- }
-
- public void updateNotificationColor() {
- Configuration currentConfig = getResources().getConfiguration();
- boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
-
- mNotificationColor = ContrastColorUtil.resolveContrastColor(mContext,
- mEntry.getSbn().getNotification().color,
- getBackgroundColorWithoutTint(), nightMode);
- }
-
public HybridNotificationView getSingleLineView() {
return mPrivateLayout.getSingleLineView();
}
@@ -2253,6 +2264,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mEntry = entry;
}
+ @VisibleForTesting
+ protected void setEntryAdapter(EntryAdapter entry) {
+ mEntryAdapter = entry;
+ }
+
private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
@@ -2490,7 +2506,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mTranslateableViews.get(i).setTranslationX(0);
}
invalidateOutline();
- getEntry().getIcons().getShelfIcon().setScrollX(0);
+ getShelfIcon().setScrollX(0);
}
if (mMenuRow != null) {
@@ -2609,7 +2625,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// In order to keep the shelf in sync with this swiping, we're simply translating
// it's icon by the same amount. The translation is already being used for the normal
// positioning, so we can use the scrollX instead.
- getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX);
+ getShelfIcon().setScrollX((int) -translationX);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
@@ -2836,7 +2852,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public @NonNull StatusBarIconView getShelfIcon() {
- return getEntry().getIcons().getShelfIcon();
+ if (NotificationBundleUi.isEnabled()) {
+ return getEntryAdapter().getIcons().getShelfIcon();
+ } else {
+ return mEntry.getIcons().getShelfIcon();
+ }
}
@Override
@@ -3065,8 +3085,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* except for legacy use cases.
*/
public boolean canShowHeadsUp() {
+ boolean canEntryHun = NotificationBundleUi.isEnabled()
+ ? mEntryAdapter.canPeek()
+ : mEntry.isStickyAndNotDemoted();
if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
- (!mEntry.isStickyAndNotDemoted()
+ (!canEntryHun
|| (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
return false;
}
@@ -3105,7 +3128,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
if (!mIsSummaryWithChildren && wasSummary) {
// Reset the 'when' once the row stops being a summary
- mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
+ if (NotificationBundleUi.isEnabled()) {
+ mPublicLayout.setNotificationWhen(mEntryAdapter.getWhen());
+ } else {
+ mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
+ }
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
@@ -3374,7 +3401,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
@Override
public boolean canExpandableViewBeDismissed() {
- if (areGutsExposed() || !mEntry.hasFinishedInitialization()) {
+ if (areGutsExposed() || !hasFinishedInitialization()) {
return false;
}
return canViewBeDismissed();
@@ -3398,7 +3425,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* clearability see {@link NotificationEntry#isClearable()}.
*/
public boolean canViewBeCleared() {
- return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ if (NotificationBundleUi.isEnabled()) {
+ return mEntryAdapter.isClearable()
+ && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ } else {
+ return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ }
}
private boolean shouldShowPublic() {
@@ -4075,6 +4107,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isMediaRow() {
+ NotificationBundleUi.assertInLegacyMode();
return mEntry.getSbn().getNotification().isMediaNotification();
}
@@ -4197,7 +4230,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void dump(PrintWriter pwOriginal, String[] args) {
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
// Skip super call; dump viewState ourselves
- pw.println("Notification: " + mEntry.getKey());
+ if (NotificationBundleUi.isEnabled()) {
+ pw.println("Notification: " + mEntryAdapter.getKey());
+ } else {
+ pw.println("Notification: " + mEntry.getKey());
+ }
DumpUtilsKt.withIncreasedIndent(pw, () -> {
pw.println(this);
pw.print("visibility: " + getVisibility());
@@ -4228,6 +4265,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
pw.print(", isPinned: " + isPinned());
pw.print(", expandedWhenPinned: " + mExpandedWhenPinned);
pw.print(", isMinimized: " + mIsMinimized);
+ pw.print(", isAboveShelf: " + isAboveShelf());
pw.println();
if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index b43a387a5edb..07711b6e0eb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -374,7 +374,11 @@ public class ExpandableNotificationRowController implements NotifViewController
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
- mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+ if (NotificationBundleUi.isEnabled()) {
+ mView.setInitializationTime(mClock.elapsedRealtime());
+ } else {
+ mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+ }
mPluginManager.addPluginListener(mView,
NotificationMenuRowPlugin.class, false /* Allow multiple */);
if (!SceneContainerFlag.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index dd3a9c9dcf21..33c36d8c4c76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.Flags.notificationRowTransparency;
import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.content.Context;
@@ -23,6 +24,7 @@ import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -34,8 +36,10 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dumpable;
+import com.android.systemui.common.shared.colors.SurfaceEffectColors;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import com.android.systemui.util.DrawableDumpKt;
@@ -69,6 +73,7 @@ public class NotificationBackgroundView extends View implements Dumpable,
private final ColorStateList mLightColoredStatefulColors;
private final ColorStateList mDarkColoredStatefulColors;
private final int mNormalColor;
+ private boolean mBgIsColorized = false;
private final int convexR = 9;
private final int concaveR = 22;
@@ -82,8 +87,12 @@ public class NotificationBackgroundView extends View implements Dumpable,
R.color.notification_state_color_light);
mDarkColoredStatefulColors = getResources().getColorStateList(
R.color.notification_state_color_dark);
- mNormalColor = mContext.getColor(
- com.android.internal.R.color.materialColorSurfaceContainerHigh);
+ if (notificationRowTransparency()) {
+ mNormalColor = SurfaceEffectColors.surfaceEffect1(getResources());
+ } else {
+ mNormalColor = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainerHigh);
+ }
mFocusOverlayStroke = getResources().getDimension(R.dimen.notification_focus_stroke_width);
}
@@ -132,6 +141,21 @@ public class NotificationBackgroundView extends View implements Dumpable,
}
}
+ /**
+ * A way to tell whether the background has been colorized.
+ */
+ public boolean isColorized() {
+ return mBgIsColorized;
+ }
+
+ /**
+ * A way to inform this class whether the background has been colorized.
+ * We need to know this, in order to *not* override that color.
+ */
+ public void setBgIsColorized(boolean b) {
+ mBgIsColorized = b;
+ }
+
private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) {
// TODO(b/365585705): Adapt to RTL after the UX design is finalized.
@@ -280,7 +304,7 @@ public class NotificationBackgroundView extends View implements Dumpable,
setCustomBackground(d);
}
- private Drawable getBaseBackgroundLayer() {
+ public Drawable getBaseBackgroundLayer() {
return ((LayerDrawable) mBackground).getDrawable(0);
}
@@ -288,11 +312,27 @@ public class NotificationBackgroundView extends View implements Dumpable,
return ((LayerDrawable) mBackground).getDrawable(1);
}
+ private void updateBaseLayerColor() {
+ // BG base layer being a drawable, there isn't a method like setColor() to color it.
+ // Instead, we set a color filter that essentially replaces every pixel of the drawable.
+ // For non-colorized notifications, this function specifies a new color token.
+ // For colorized notifications, this uses a color that matches the tint color at 90% alpha.
+ getBaseBackgroundLayer().setColorFilter(
+ new PorterDuffColorFilter(
+ isColorized()
+ ? ColorUtils.setAlphaComponent(mTintColor, (int) (255 * 0.9f))
+ : SurfaceEffectColors.surfaceEffect1(getResources()),
+ PorterDuff.Mode.SRC)); // SRC operator discards the drawable's color+alpha
+ }
+
public void setTint(int tintColor) {
Drawable baseLayer = getBaseBackgroundLayer();
baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP);
baseLayer.setTint(tintColor);
mTintColor = tintColor;
+ if (notificationRowTransparency()) {
+ updateBaseLayerColor();
+ }
setStatefulColors();
invalidate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6e638f5de209..9a75253295d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -73,6 +73,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -436,7 +437,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
onNasFeedbackClick,
mUiEventLogger,
mDeviceProvisionedController.isDeviceProvisioned(),
- row.getIsNonblockable(),
+ NotificationBundleUi.isEnabled()
+ ? !row.getEntry().isBlockable()
+ : row.getIsNonblockable(),
mHighPriorityProvider.isHighPriority(row.getEntry()),
mAssistantFeedbackController,
mMetricsLogger,
@@ -480,7 +483,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
row.getEntry(),
onSettingsClick,
mDeviceProvisionedController.isDeviceProvisioned(),
- row.getIsNonblockable());
+ NotificationBundleUi.isEnabled()
+ ? !row.getEntry().isBlockable()
+ : row.getIsNonblockable());
}
/**
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 b96b224a7d2e..ab382df13d10 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
@@ -186,7 +186,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
@Override
- public void createMenu(ViewGroup parent, StatusBarNotification sbn) {
+ public void createMenu(ViewGroup parent) {
mParent = (ExpandableNotificationRow) parent;
createMenuViews(true /* resetState */);
}
@@ -227,7 +227,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
@Override
- public void onNotificationUpdated(StatusBarNotification sbn) {
+ public void onNotificationUpdated() {
if (mMenuContainer == null) {
// Menu hasn't been created yet, no need to do anything.
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index 9d13ab53ec71..6a96fba8f28d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.notification.row.wrapper
import android.app.Flags
+import android.app.Flags.notificationsRedesignTemplates
import android.content.Context
import android.graphics.drawable.AnimatedImageDrawable
import android.view.View
import android.view.ViewGroup
+import androidx.core.view.isVisible
import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.MessagingGroup
@@ -53,8 +55,8 @@ class NotificationConversationTemplateViewWrapper(
private lateinit var badgeIconView: NotificationRowIconView
private lateinit var conversationBadgeBg: View
private lateinit var expandBtn: View
- private lateinit var expandBtnContainer: View
- private lateinit var imageMessageContainer: ViewGroup
+ private var expandBtnContainer: View? = null
+ private var imageMessageContainer: ViewGroup? = null
private lateinit var messageContainers: ArrayList<MessagingGroup>
private lateinit var messagingLinearLayout: MessagingLinearLayout
private lateinit var conversationTitleView: View
@@ -78,10 +80,14 @@ class NotificationConversationTemplateViewWrapper(
conversationBadgeBg =
requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
expandBtn = requireViewById(com.android.internal.R.id.expand_button)
- expandBtnContainer = requireViewById(com.android.internal.R.id.expand_button_container)
+ expandBtnContainer = findViewById(com.android.internal.R.id.expand_button_container)
importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
appName = requireViewById(com.android.internal.R.id.app_name_text)
- conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
+ conversationTitleView =
+ requireViewById(
+ if (notificationsRedesignTemplates()) com.android.internal.R.id.title
+ else com.android.internal.R.id.conversation_text
+ )
facePileTop = findViewById(com.android.internal.R.id.conversation_face_pile_top)
facePileBottom = findViewById(com.android.internal.R.id.conversation_face_pile_bottom)
facePileBottomBg =
@@ -133,11 +139,21 @@ class NotificationConversationTemplateViewWrapper(
expandable: Boolean,
onClickListener: View.OnClickListener,
requestLayout: Boolean,
- ) = conversationLayout.updateExpandability(expandable, onClickListener)
+ ) {
+ if (notificationsRedesignTemplates()) {
+ super.updateExpandability(expandable, onClickListener, requestLayout)
+ } else {
+ conversationLayout.updateExpandability(expandable, onClickListener)
+ }
+ }
override fun disallowSingleClick(x: Float, y: Float): Boolean {
val isOnExpandButton =
- expandBtnContainer.visibility == View.VISIBLE && isOnView(expandBtnContainer, x, y)
+ if (notificationsRedesignTemplates()) {
+ expandBtn.isVisible && isOnView(expandBtn, x, y)
+ } else {
+ expandBtnContainer?.visibility == View.VISIBLE && isOnView(expandBtnContainer, x, y)
+ }
return isOnExpandButton || super.disallowSingleClick(x, y)
}
@@ -158,7 +174,8 @@ class NotificationConversationTemplateViewWrapper(
// and the top level image message container.
val containers =
messageContainers.asSequence().map { it.messageContainer } +
- sequenceOf(imageMessageContainer)
+ if (notificationsRedesignTemplates()) emptySequence()
+ else sequenceOf(imageMessageContainer!!)
val drawables =
containers
.flatMap { it.children }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3d60e03d7ca4..3ff18efeae53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -6686,7 +6686,7 @@ public class NotificationStackScrollLayout
static boolean canChildBeCleared(View v) {
if (v instanceof ExpandableNotificationRow row) {
- if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
+ if (row.areGutsExposed() || !row.hasFinishedInitialization()) {
return false;
}
return row.canViewBeCleared();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 06b989aaab57..08692bea2292 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -1227,13 +1227,17 @@ public class StackScrollAlgorithm {
float baseZ = ambientState.getBaseZHeight();
if (SceneContainerFlag.isEnabled()) {
- // SceneContainer flags off this logic, and just sets the baseZ because:
+ // SceneContainer simplifies this logic, because:
// - there are no overlapping HUNs anymore, no need for multiplying their shadows
// - shadows for HUNs overlapping with the stack are now set from updateHeadsUpStates
- // - shadows for HUNs overlapping with the shelf are NOT set anymore, because it only
- // happens on AOD/Pulsing, where they're displayed on a black background so a shadow
- // wouldn't be visible.
- childViewState.setZTranslation(baseZ);
+ if (child.isPinned() || ambientState.getTrackedHeadsUpRow() == child) {
+ // set a default elevation on the HUN, which would be overridden
+ // from updateHeadsUpStates if it is displayed in the shade
+ childViewState.setZTranslation(baseZ + mPinnedZTranslationExtra);
+ } else {
+ // set baseZ for every notification
+ childViewState.setZTranslation(baseZ);
+ }
} else {
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
&& !ambientState.isDozingAndNotPulsing(child)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 54efa4a2bcf2..34b65603542c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import android.content.Context
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.flow.flowName
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -44,15 +45,18 @@ import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToDozingTransitionViewModel
@@ -84,7 +88,9 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.sample
@@ -135,7 +141,9 @@ constructor(
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+ private val aodToGlanceableHubTransitionViewModel: AodToGlanceableHubTransitionViewModel,
private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
+ private val dozingToDreamingTransitionViewModel: DozingToDreamingTransitionViewModel,
dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
@@ -144,6 +152,7 @@ constructor(
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
+ private val glanceableHubToAodTransitionViewModel: GlanceableHubToAodTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
private val goneToDreamingTransitionViewModel: GoneToDreamingTransitionViewModel,
@@ -293,20 +302,40 @@ constructor(
.distinctUntilChanged()
.dumpWhileCollecting("configurationBasedDimensions")
+ private val isOnAnyBouncer: Flow<Boolean> =
+ anyOf(
+ keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER).map { it > 0f },
+ keyguardTransitionInteractor
+ .transitionValue(
+ scene = Scenes.Bouncer,
+ stateWithoutSceneContainer = PRIMARY_BOUNCER,
+ )
+ .map { it > 0f },
+ )
+
/** If the user is visually on one of the unoccluded lockscreen states. */
val isOnLockscreen: Flow<Boolean> =
- anyOf(
- keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f },
- keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f },
- keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER).map { it > 0f },
- keyguardTransitionInteractor
- .transitionValue(
- scene = Scenes.Bouncer,
- stateWithoutSceneContainer = PRIMARY_BOUNCER,
- )
- .map { it > 0f },
- keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
- )
+ if (glanceableHubV2()) {
+ anyOf(
+ keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f },
+ keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f },
+ keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
+ allOf(
+ // Exclude bouncer showing over communal hub, as this should not be
+ // considered
+ // "lockscreen"
+ not(communalSceneInteractor.isCommunalVisible),
+ isOnAnyBouncer,
+ ),
+ )
+ } else {
+ anyOf(
+ keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f },
+ keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f },
+ keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
+ isOnAnyBouncer,
+ )
+ }
.flowName("isOnLockscreen")
.stateIn(
scope = applicationScope,
@@ -571,7 +600,9 @@ constructor(
aodToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ aodToGlanceableHubTransitionViewModel.lockscreenAlpha(viewState),
aodToPrimaryBouncerTransitionViewModel.notificationAlpha,
+ dozingToDreamingTransitionViewModel.notificationAlpha,
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToPrimaryBouncerTransitionViewModel.notificationAlpha,
@@ -591,6 +622,7 @@ constructor(
offToLockscreenTransitionViewModel.lockscreenAlpha,
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ glanceableHubToAodTransitionViewModel.lockscreenAlpha,
lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
)
}
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 de7215461c80..36193bd87ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -441,6 +441,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mAnimationScheduler.addCallback(mAnimationCallback);
mUserInfoController.addCallback(mOnUserInfoChangedListener);
mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mStatusBarState = mStatusBarStateController.getState();
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId());
if (mTintedIconManager == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 6c8e1825ea0a..ba41fd4c40ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -65,6 +65,7 @@ import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -116,6 +117,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final static String TAG = "StatusBarNotificationActivityStarter";
private final Context mContext;
+ private final ShadeDialogContextInteractor mContextInteractor;
private final Handler mMainThreadHandler;
private final Executor mUiBgExecutor;
@@ -156,6 +158,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Inject
StatusBarNotificationActivityStarter(
@ShadeDisplayAware Context context,
+ ShadeDialogContextInteractor contextInteractor,
@Main Handler mainThreadHandler,
@Background Executor uiBgExecutor,
NotificationVisibilityProvider visibilityProvider,
@@ -188,6 +191,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
PowerInteractor powerInteractor,
UserTracker userTracker) {
mContext = context;
+ mContextInteractor = contextInteractor;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
mVisibilityProvider = visibilityProvider;
@@ -491,7 +495,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean animate,
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry);
- final int displayId = mContext.getDisplayId();
+ final int displayId = mContextInteractor.getContext().getDisplayId();
try {
ActivityTransitionAnimator.Controller animationController =
new StatusBarTransitionAnimatorController(
@@ -532,7 +536,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
public void startNotificationGutsIntent(@NonNull final Intent intent, final int appUid,
@NonNull ExpandableNotificationRow row) {
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
- final int displayId = mContext.getDisplayId();
+ final int displayId = mContextInteractor.getContext().getDisplayId();
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
public boolean onDismiss() {
@@ -571,7 +575,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startHistoryIntent(View view, boolean showHistory) {
ModesEmptyShadeFix.assertInLegacyMode();
- final int displayId = mContext.getDisplayId();
+ final int displayId = mContextInteractor.getContext().getDisplayId();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -621,7 +625,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startSettingsIntent(@NonNull View view, @NonNull SettingsIntent intentInfo) {
- final int displayId = mContext.getDisplayId();
+ final int displayId = mContextInteractor.getContext().getDisplayId();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index c52536d2b312..cee685a3f47a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -21,7 +21,6 @@ import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
import com.android.settingslib.SignalIcon.MobileIconGroup
-import com.android.settingslib.flags.Flags
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -29,6 +28,7 @@ import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -85,6 +85,12 @@ interface MobileIconsInteractor {
/** Whether the mobile icons can be stacked vertically. */
val isStackable: StateFlow<Boolean>
+ /**
+ * Observable for the subscriptionId of the current mobile data connection. Null if we don't
+ * have a valid subscription id
+ */
+ val activeMobileDataSubscriptionId: StateFlow<Int?>
+
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
@@ -168,6 +174,9 @@ constructor(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val activeMobileDataSubscriptionId: StateFlow<Int?> =
+ mobileConnectionsRepo.activeMobileDataSubscriptionId
+
override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
mobileConnectionsRepo.activeMobileDataRepository
.flatMapLatest { it?.dataEnabled ?: flowOf(false) }
@@ -298,7 +307,7 @@ constructor(
.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
override val isStackable =
- if (Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled) {
+ if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) {
icons.flatMapLatest { icons ->
combine(icons.map { it.isNonTerrestrial }) {
it.size == 2 && it.none { isNonTerrestrial -> isNonTerrestrial }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 6176a3e9e281..288e49eac5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -63,6 +63,8 @@ constructor(
@VisibleForTesting
val reuseCache = ConcurrentHashMap<Int, Pair<MobileIconViewModel, CoroutineScope>>()
+ val activeMobileDataSubscriptionId: StateFlow<Int?> = interactor.activeMobileDataSubscriptionId
+
val subscriptionIdsFlow: StateFlow<List<Int>> =
interactor.filteredSubscriptions
.mapLatest { subscriptions ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
index a2c2a3cd1507..2c85a5150575 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
@@ -25,7 +25,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconMod
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -43,8 +43,14 @@ constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable()
initialValue = false,
)
- private val iconViewModelFlow: StateFlow<List<MobileIconViewModelCommon>> =
- mobileIconsViewModel.mobileSubViewModels
+ private val iconViewModelFlow: Flow<List<MobileIconViewModelCommon>> =
+ combine(
+ mobileIconsViewModel.mobileSubViewModels,
+ mobileIconsViewModel.activeMobileDataSubscriptionId,
+ ) { viewModels, activeSubId ->
+ // Sort to get the active subscription first, if it's set
+ viewModels.sortedByDescending { it.subscriptionId == activeSubId }
+ }
val dualSim: DualSim? by
hydrator.hydratedStateOf(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index cd320a12d577..d7348892356d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -31,6 +31,8 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
+import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipViewBinding
+import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarRootModernization
@@ -149,6 +151,7 @@ constructor(
if (!StatusBarNotifChips.isEnabled && !StatusBarChipsModernization.isEnabled) {
val primaryChipViewBinding =
OngoingActivityChipBinder.createBinding(primaryChipView)
+
launch {
viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
OngoingActivityChipBinder.bind(
@@ -156,18 +159,14 @@ constructor(
primaryChipViewBinding,
iconViewStore,
)
- if (StatusBarRootModernization.isEnabled) {
- when (primaryChipModel) {
- is OngoingActivityChipModel.Active ->
- primaryChipViewBinding.rootView.show(
- shouldAnimateChange = true
- )
- is OngoingActivityChipModel.Inactive ->
- primaryChipViewBinding.rootView.hide(
- state = View.GONE,
- shouldAnimateChange = primaryChipModel.shouldAnimate,
- )
+ if (StatusBarRootModernization.isEnabled) {
+ launch {
+ bindLegacyPrimaryOngoingActivityChipWithVisibility(
+ viewModel,
+ primaryChipModel,
+ primaryChipViewBinding,
+ )
}
} else {
when (primaryChipModel) {
@@ -213,12 +212,14 @@ constructor(
)
if (StatusBarRootModernization.isEnabled) {
- primaryChipViewBinding.rootView.adjustVisibility(
- chips.primary.toVisibilityModel()
- )
- secondaryChipViewBinding.rootView.adjustVisibility(
- chips.secondary.toVisibilityModel()
- )
+ launch {
+ bindOngoingActivityChipsWithVisibility(
+ viewModel,
+ chips,
+ primaryChipViewBinding,
+ secondaryChipViewBinding,
+ )
+ }
} else {
listener?.onOngoingActivityStatusChanged(
hasPrimaryOngoingActivity =
@@ -312,6 +313,52 @@ constructor(
}
}
+ /** Bind the (legacy) single primary ongoing activity chip with the status bar visibility */
+ private suspend fun bindLegacyPrimaryOngoingActivityChipWithVisibility(
+ viewModel: HomeStatusBarViewModel,
+ primaryChipModel: OngoingActivityChipModel,
+ primaryChipViewBinding: OngoingActivityChipViewBinding,
+ ) {
+ viewModel.canShowOngoingActivityChips.collectLatest { visible ->
+ if (!visible) {
+ primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ } else {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Active -> {
+ primaryChipViewBinding.rootView.show(shouldAnimateChange = true)
+ }
+
+ is OngoingActivityChipModel.Inactive -> {
+ primaryChipViewBinding.rootView.hide(
+ state = View.GONE,
+ shouldAnimateChange = primaryChipModel.shouldAnimate,
+ )
+ }
+ }
+ }
+ }
+ }
+
+ /** Bind the primary/secondary chips along with the home status bar's visibility */
+ private suspend fun bindOngoingActivityChipsWithVisibility(
+ viewModel: HomeStatusBarViewModel,
+ chips: MultipleOngoingActivityChipsModelLegacy,
+ primaryChipViewBinding: OngoingActivityChipViewBinding,
+ secondaryChipViewBinding: OngoingActivityChipViewBinding,
+ ) {
+ viewModel.canShowOngoingActivityChips.collectLatest { canShow ->
+ if (!canShow) {
+ primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ secondaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ } else {
+ primaryChipViewBinding.rootView.adjustVisibility(chips.primary.toVisibilityModel())
+ secondaryChipViewBinding.rootView.adjustVisibility(
+ chips.secondary.toVisibilityModel()
+ )
+ }
+ }
+ }
+
private fun SystemEventAnimationState.isAnimatingChip() =
when (this) {
AnimatingIn,
@@ -374,43 +421,42 @@ constructor(
if (visibility == View.INVISIBLE || visibility == View.GONE) {
return
}
+ alpha = 0f
visibility = state
}
// See CollapsedStatusBarFragment#hide.
private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) {
+ animate().cancel()
if (visibility == View.INVISIBLE || visibility == View.GONE) {
return
}
- val v = this
- v.animate().cancel()
if (!shouldAnimateChange) {
- v.alpha = 0f
- v.visibility = state
+ alpha = 0f
+ visibility = state
return
}
- v.animate()
+ animate()
.alpha(0f)
.setDuration(CollapsedStatusBarFragment.FADE_OUT_DURATION.toLong())
.setStartDelay(0)
.setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction { v.visibility = state }
+ .withEndAction { visibility = state }
}
// See CollapsedStatusBarFragment#show.
private fun View.show(shouldAnimateChange: Boolean) {
- if (visibility == View.VISIBLE) {
+ animate().cancel()
+ if (visibility == View.VISIBLE && alpha >= 1f) {
return
}
- val v = this
- v.animate().cancel()
- v.visibility = View.VISIBLE
+ visibility = View.VISIBLE
if (!shouldAnimateChange) {
- v.alpha = 1f
+ alpha = 1f
return
}
- v.animate()
+ animate()
.alpha(1f)
.setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION.toLong())
.setInterpolator(Interpolators.ALPHA_IN)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
index d3e37119fdcb..2433d112bc69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
/** Model describing the state that the QS Internet tile should be in. */
sealed interface InternetTileModel {
@@ -49,11 +50,14 @@ sealed interface InternetTileModel {
state.contentDescription = contentDescription.loadContentDescription(context)
// To support both SignalDrawable and other icons, give priority to icons over IDs
- if (icon != null) {
- state.icon = icon
- } else if (iconId != null) {
- state.icon = QSTileImpl.maybeLoadResourceIcon(iconId!!, context)
- }
+ state.icon =
+ when {
+ icon is ResourceIcon ->
+ QSTileImpl.maybeLoadResourceIcon((icon as ResourceIcon).resId, context)
+ icon != null -> icon
+ iconId != null -> QSTileImpl.maybeLoadResourceIcon(iconId!!, context)
+ else -> null
+ }
state.state =
if (this is Active) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 9ae2cb203d91..807e90567eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -149,6 +149,9 @@ interface HomeStatusBarViewModel : Activatable {
*/
val isHomeStatusBarAllowedByScene: StateFlow<Boolean>
+ /** True if the home status bar is showing, and there is no HUN happening */
+ val canShowOngoingActivityChips: Flow<Boolean>
+
/** True if the operator name view is not hidden due to HUN or other visibility state */
val shouldShowOperatorNameView: Flow<Boolean>
val isClockVisible: Flow<VisibilityModel>
@@ -412,6 +415,15 @@ constructor(
)
.flowOn(bgDispatcher)
+ override val canShowOngoingActivityChips: Flow<Boolean> =
+ combine(
+ isHomeStatusBarAllowed,
+ keyguardInteractor.isSecureCameraActive,
+ headsUpNotificationInteractor.statusBarHeadsUpStatus,
+ ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
+ isHomeStatusBarAllowed && !isSecureCameraActive && !headsUpState.isPinned
+ }
+
override val isClockVisible: Flow<VisibilityModel> =
combine(
shouldHomeStatusBarBeVisible,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 88cf46a0ca07..b13e01be40f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -29,7 +29,6 @@ import androidx.annotation.NonNull;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
@@ -43,7 +42,6 @@ import javax.inject.Inject;
* Handles reading and writing of rotation lock settings per device state, as well as setting the
* rotation lock when device state changes.
*/
-@SysUISingleton
public final class DeviceStateRotationLockSettingController
implements Listenable, RotationLockController.RotationLockControllerCallback, Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 797aa1f3a3dd..3ee7f33e3d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -27,8 +27,10 @@ import androidx.annotation.NonNull;
import com.android.internal.view.RotationPolicy.RotationPolicyListener;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.rotationlock.DeviceStateAutoRotateModule.BoundsDeviceStateAutoRotateModule;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
+import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
@@ -50,21 +52,25 @@ public final class RotationLockControllerImpl implements RotationLockController
};
private final RotationPolicyWrapper mRotationPolicy;
- private final DeviceStateRotationLockSettingController
+ private final Optional<DeviceStateRotationLockSettingController>
mDeviceStateRotationLockSettingController;
private final boolean mIsPerDeviceStateRotationLockEnabled;
@Inject
public RotationLockControllerImpl(
RotationPolicyWrapper rotationPolicyWrapper,
- DeviceStateRotationLockSettingController deviceStateRotationLockSettingController,
+ Optional<DeviceStateRotationLockSettingController>
+ deviceStateRotationLockSettingController,
@Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
) {
mRotationPolicy = rotationPolicyWrapper;
- mDeviceStateRotationLockSettingController = deviceStateRotationLockSettingController;
mIsPerDeviceStateRotationLockEnabled = deviceStateRotationLockDefaults.length > 0;
- if (mIsPerDeviceStateRotationLockEnabled) {
- mCallbacks.add(mDeviceStateRotationLockSettingController);
+ mDeviceStateRotationLockSettingController =
+ deviceStateRotationLockSettingController;
+
+ if (mIsPerDeviceStateRotationLockEnabled
+ && mDeviceStateRotationLockSettingController.isPresent()) {
+ mCallbacks.add(mDeviceStateRotationLockSettingController.get());
}
setListening(true);
@@ -113,8 +119,9 @@ public final class RotationLockControllerImpl implements RotationLockController
} else {
mRotationPolicy.unregisterRotationPolicyListener(mRotationPolicyListener);
}
- if (mIsPerDeviceStateRotationLockEnabled) {
- mDeviceStateRotationLockSettingController.setListening(listening);
+ if (mIsPerDeviceStateRotationLockEnabled
+ && mDeviceStateRotationLockSettingController.isPresent()) {
+ mDeviceStateRotationLockSettingController.get().setListening(listening);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
index 33ed419afef2..5dbe9b145bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
@@ -21,29 +21,25 @@ import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.qs.QSModesEvent
import javax.inject.Inject
-class ModesDialogEventLogger
-@Inject
-constructor(
- private val uiEventLogger: UiEventLogger,
-) {
+class ModesDialogEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) {
fun logModeOn(mode: ZenMode) {
val id =
if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_ON else QSModesEvent.QS_MODES_MODE_ON
- uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ uiEventLogger.log(id, /* uid= */ 0, mode.ownerPackage)
}
fun logModeOff(mode: ZenMode) {
val id =
if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_OFF else QSModesEvent.QS_MODES_MODE_OFF
- uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ uiEventLogger.log(id, /* uid= */ 0, mode.ownerPackage)
}
fun logModeSettings(mode: ZenMode) {
val id =
if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_SETTINGS
else QSModesEvent.QS_MODES_MODE_SETTINGS
- uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ uiEventLogger.log(id, /* uid= */ 0, mode.ownerPackage)
}
fun logOpenDurationDialog(mode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 07f1c3470c83..dc07202dc486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -71,10 +71,10 @@ constructor(
mode.id in prevIds -> true
// Mode is enabled -> show if active (so user can toggle off), or if it
// can be manually toggled on
- mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
+ mode.isEnabled -> mode.isActive || mode.isManualInvocationAllowed
// Mode was created as disabled, or disabled by the app that owns it ->
// will be shown with a "Not set" text
- !mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
+ !mode.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
else -> false
}
}
@@ -97,13 +97,13 @@ constructor(
if (mode.isActive) R.string.zen_mode_on else R.string.zen_mode_off
),
onClick = {
- if (!mode.rule.isEnabled) {
+ if (!mode.isEnabled) {
openSettings(mode)
} else if (mode.isActive) {
dialogEventLogger.logModeOff(mode)
zenModeInteractor.deactivateMode(mode)
} else {
- if (mode.rule.isManualInvocationAllowed) {
+ if (mode.isManualInvocationAllowed) {
if (zenModeInteractor.shouldAskForZenDuration(mode)) {
dialogEventLogger.logOpenDurationDialog(mode)
// NOTE: The dialog handles turning on the mode itself.
@@ -144,10 +144,10 @@ constructor(
* readers, and for the tile subtext will be augmented with the current status of the mode.
*/
private fun getModeDescription(mode: ZenMode, forAccessibility: Boolean): String? {
- if (!mode.rule.isEnabled) {
+ if (!mode.isEnabled) {
return context.resources.getString(R.string.zen_mode_set_up)
}
- if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
+ if (!mode.isManualInvocationAllowed && !mode.isActive) {
return context.resources.getString(R.string.zen_mode_no_manual_invocation)
}
return if (forAccessibility)
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index 32f2ca6fb696..367f54cf4936 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -147,7 +147,7 @@ public class CreateUserActivity extends Activity {
super.onDestroy();
}
- private void addUserNow(String userName, Drawable userIcon, Boolean isAdmin) {
+ private void addUserNow(String userName, Drawable userIcon, String iconPath, Boolean isAdmin) {
mSetupUserDialog.dismiss();
userName = (userName == null || userName.trim().isEmpty())
? getString(com.android.settingslib.R.string.user_new_user_name)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 4ff09d3bc6af..e8b50d580c33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -45,6 +45,9 @@ import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
@@ -74,10 +77,14 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -127,6 +134,9 @@ public class ShadeListBuilderTest extends SysuiTestCase {
private TestableStabilityManager mStabilityManager;
private TestableNotifFilter mFinalizeFilter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Map<String, Integer> mNextIdMap = new ArrayMap<>();
private int mNextRank = 0;
@@ -561,6 +571,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
public void testFilter_resetsInitalizationTime() {
// GIVEN a NotifFilter that filters out a specific package
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1));
@@ -584,6 +595,31 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void testFilter_resetsInitializationTime_onRow() throws Exception {
+ // GIVEN a NotifFilter that filters out a specific package
+ NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1));
+ mListBuilder.addFinalizeFilter(filter1);
+
+ // GIVEN a notification that was initialized 1 second ago that will be filtered out
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(PACKAGE_1)
+ .setId(nextId(PACKAGE_1))
+ .setRank(nextRank())
+ .build();
+ entry.setRow(new NotificationTestHelper(mContext, mDependency).createRow());
+ entry.getRow().setInitializationTime(SystemClock.elapsedRealtime() - 1000);
+ assertTrue(entry.getRow().hasFinishedInitialization());
+
+ // WHEN the pipeline is kicked off
+ mReadyForBuildListener.onBuildList(singletonList(entry), "test");
+ mPipelineChoreographer.runIfScheduled();
+
+ // THEN the entry's initialization time is reset
+ assertFalse(entry.getRow().hasFinishedInitialization());
+ }
+
+ @Test
public void testNotifFiltersCanBePreempted() {
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 1b5353127f25..24d8d1cc8fae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -75,6 +75,7 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -589,6 +590,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
public void testGetIsNonblockable() throws Exception {
ExpandableNotificationRow row =
mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
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 47238fedee4d..c874bc6056c6 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
@@ -36,6 +36,7 @@ import com.android.internal.widget.NotificationActionListLayout
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.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
@@ -82,6 +83,7 @@ class NotificationContentViewTest : SysuiTestCase() {
fakeParent =
spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
val mockEntry = createMockNotificationEntry()
+ val mockEntryAdapter = createMockNotificationEntryAdapter()
row =
spy(
when (NotificationBundleUi.isEnabled) {
@@ -92,6 +94,7 @@ class NotificationContentViewTest : SysuiTestCase() {
UserHandle.CURRENT
).apply {
entry = mockEntry
+ entryAdapter = mockEntryAdapter
}
}
false -> {
@@ -611,6 +614,7 @@ class NotificationContentViewTest : SysuiTestCase() {
whenever(this.entry).thenReturn(notificationEntry)
whenever(this.context).thenReturn(mContext)
whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
+ whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter())
}
private fun createMockNotificationEntry() =
@@ -624,6 +628,9 @@ class NotificationContentViewTest : SysuiTestCase() {
whenever(sbnMock.user).thenReturn(userMock)
}
+ private fun createMockNotificationEntryAdapter() =
+ mock<EntryAdapter>()
+
private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
val outerLayout = LinearLayout(mContext)
val innerLayout = LinearLayout(mContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 3d4c90140adb..99b99ee1860b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -427,7 +427,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
- whenever(row.getIsNonblockable()).thenReturn(false)
whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -463,7 +462,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- whenever(row.getIsNonblockable()).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -499,7 +497,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- whenever(row.getIsNonblockable()).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -566,7 +563,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
): NotificationMenuRowPlugin.MenuItem {
val menuRow: NotificationMenuRowPlugin =
NotificationMenuRow(mContext, peopleNotificationIdentifier)
- menuRow.createMenu(row, row!!.entry.sbn)
+ menuRow.createMenu(row)
val menuItem = menuRow.getLongpressMenuItem(mContext)
Assert.assertNotNull(menuItem)
return menuItem
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 6ac20d40f2dc..955de273c426 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
@@ -675,7 +675,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testClearNotifications_clearAllInProgress() {
ExpandableNotificationRow row = createClearableRow();
- when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
+ when(row.hasFinishedInitialization()).thenReturn(true);
doReturn(true).when(mStackScroller).isVisible(row);
mStackScroller.addContainerView(row);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 3190d3ae8f16..28eafa937097 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -76,6 +76,7 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
import com.android.systemui.statusbar.CommandQueue;
@@ -171,6 +172,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private ExpandableNotificationRow mNotificationRow;
private ExpandableNotificationRow mBubbleNotificationRow;
+ private FakeShadeDialogContextInteractor mContextInteractor;
private final Answer<Void> mCallOnDismiss = answerVoid(
(OnDismissAction dismissAction, Runnable cancel,
@@ -187,6 +189,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mDependency,
TestableLooper.get(this));
+ mContextInteractor = new FakeShadeDialogContextInteractor(mContext);
+
// Create standard notification with contentIntent
mNotificationRow = notificationTestHelper.createRow();
StatusBarNotification sbn = mNotificationRow.getEntry().getSbn();
@@ -199,10 +203,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
bubbleSbn.getNotification().contentIntent = mContentIntent;
bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
-// ArrayList<NotificationEntry> activeNotifications = new ArrayList<>();
-// activeNotifications.add(mNotificationRow.getEntry());
-// activeNotifications.add(mBubbleNotificationRow.getEntry());
-// when(mEntryManager.getVisibleNotifications()).thenReturn(activeNotifications);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mOnUserInteractionCallback.registerFutureDismissal(eq(mNotificationRow.getEntry()),
anyInt())).thenReturn(mFutureDismissalRunnable);
@@ -232,6 +232,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mNotificationActivityStarter =
new StatusBarNotificationActivityStarter(
getContext(),
+ mContextInteractor,
mHandler,
mUiBgExecutor,
mVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 0f21a16147f0..b8b2ec5a58ae 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.domain.interactor
+import android.content.pm.UserInfo
import android.content.testableContext
import android.os.userManager
import com.android.systemui.broadcast.broadcastDispatcher
@@ -85,27 +86,28 @@ fun Kosmos.setCommunalV2ConfigEnabled(enabled: Boolean) {
)
}
-suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) {
+suspend fun Kosmos.setCommunalEnabled(enabled: Boolean): UserInfo {
fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled)
- if (enabled) {
+ return if (enabled) {
fakeUserRepository.asMainUser()
} else {
fakeUserRepository.asDefaultUser()
}
}
-suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) {
+suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean): UserInfo {
setCommunalV2ConfigEnabled(enabled)
- setCommunalEnabled(enabled)
+ return setCommunalEnabled(enabled)
}
-suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
- setCommunalEnabled(available)
+suspend fun Kosmos.setCommunalAvailable(available: Boolean): UserInfo {
+ val user = setCommunalEnabled(available)
fakeKeyguardRepository.setKeyguardShowing(available)
fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, available)
+ return user
}
-suspend fun Kosmos.setCommunalV2Available(available: Boolean) {
+suspend fun Kosmos.setCommunalV2Available(available: Boolean): UserInfo {
setCommunalV2ConfigEnabled(available)
- setCommunalAvailable(available)
+ return setCommunalAvailable(available)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index 93a59eb8ca0c..bdfa875f5429 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -16,6 +16,9 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -38,5 +41,8 @@ val Kosmos.fromAodTransitionInteractor by
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
+ communalSceneInteractor = communalSceneInteractor,
+ communalInteractor = communalInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModelKosmos.kt
new file mode 100644
index 000000000000..1eeecd4b7520
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.glanceableHubBlurComponentFactory
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.aodToGlanceableHubTransitionViewModel by
+ Kosmos.Fixture {
+ AodToGlanceableHubTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ blurFactory = glanceableHubBlurComponentFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
index bd0045501ec8..2797b4409ff0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
@@ -47,5 +47,6 @@ val Kosmos.deviceEntryBackgroundViewModel by Fixture {
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
+ glanceableHubToAodTransitionViewModel = glanceableHubToAodTransitionViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToAodTransitionViewModelKosmos.kt
new file mode 100644
index 000000000000..6004c7f2caec
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToAodTransitionViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.glanceableHubToAodTransitionViewModel by
+ Kosmos.Fixture {
+ GlanceableHubToAodTransitionViewModel(
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 78356318cbb4..27ca0f867855 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -94,5 +94,7 @@ val Kosmos.keyguardRootViewModel by Fixture {
shadeInteractor = shadeInteractor,
wallpaperFocalAreaInteractor = wallpaperFocalAreaInteractor,
dumpManager = dumpManager,
+ glanceableHubToAodTransitionViewModel = glanceableHubToAodTransitionViewModel,
+ aodToGlanceableHubTransitionViewModel = aodToGlanceableHubTransitionViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
index 8d4db8b74061..8a6f68c5da68 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.panels.domain.interactor
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.core.FakeLogBuffer
@@ -29,6 +30,7 @@ val Kosmos.iconTilesInteractor by
defaultLargeTilesRepository,
currentTilesInteractor,
qsPreferencesInteractor,
+ uiEventLoggerFake,
largeTileSpanRepository,
FakeLogBuffer.Factory.create(),
applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt
index 6fd7cf6edbe4..2ea2119970ad 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt
@@ -16,14 +16,18 @@
package com.android.systemui.shade.domain.interactor
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.activityStarter
import com.android.systemui.shade.data.repository.shadeHeaderClockRepository
+import com.android.systemui.util.time.systemClock
var Kosmos.shadeHeaderClockInteractor: ShadeHeaderClockInteractor by
Kosmos.Fixture {
ShadeHeaderClockInteractor(
repository = shadeHeaderClockRepository,
activityStarter = activityStarter,
+ broadcastDispatcher = broadcastDispatcher,
+ systemClock = systemClock,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
index 08de73be1128..34e5bfde43c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -18,7 +18,6 @@ package com.android.systemui.shade.ui.viewmodel
import android.content.applicationContext
import com.android.systemui.battery.batteryMeterViewControllerFactory
-import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -26,7 +25,6 @@ import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeModeInteractor
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.tintedIconManagerFactory
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
@@ -48,9 +46,6 @@ val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
tintedIconManagerFactory = tintedIconManagerFactory,
batteryMeterViewControllerFactory = batteryMeterViewControllerFactory,
statusBarIconController = mock<StatusBarIconController>(),
- notificationIconContainerStatusBarViewBinder =
- mock<NotificationIconContainerStatusBarViewBinder>(),
- broadcastDispatcher = broadcastDispatcher,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 7a2b7c24252b..51bb94fd2ab9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -25,15 +25,18 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.goneToDozingTransitionViewModel
@@ -81,6 +84,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
+ dozingToDreamingTransitionViewModel = dozingToDreamingTransitionViewModel,
dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
@@ -110,5 +114,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
headsUpNotificationInteractor = { headsUpNotificationInteractor },
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper },
unfoldTransitionInteractor = unfoldTransitionInteractor,
+ glanceableHubToAodTransitionViewModel = glanceableHubToAodTransitionViewModel,
+ aodToGlanceableHubTransitionViewModel = aodToGlanceableHubTransitionViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index d787e2c190c8..91404e0688ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -30,6 +30,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.activityStarter
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
import com.android.systemui.shade.shadeController
@@ -52,6 +53,7 @@ val Kosmos.statusBarNotificationActivityStarter by
Kosmos.Fixture {
StatusBarNotificationActivityStarter(
applicationContext,
+ shadeDialogContextInteractor,
fakeExecutorHandler,
fakeExecutor,
notificationVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
index d09d010cba2e..8ff7c7d01fb3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
@@ -76,6 +76,7 @@ object OngoingCallTestHelper {
promotedContent: PromotedNotificationContentModel? = null,
contentIntent: PendingIntent? = null,
uid: Int = DEFAULT_UID,
+ appName: String = "Fake name",
) {
if (StatusBarChipsModernization.isEnabled) {
activeNotificationListRepository.addNotif(
@@ -87,6 +88,7 @@ object OngoingCallTestHelper {
contentIntent = contentIntent,
promotedContent = promotedContent,
uid = uid,
+ appName = appName,
)
)
} else {
@@ -96,6 +98,7 @@ object OngoingCallTestHelper {
notificationIcon = statusBarChipIconView,
intent = contentIntent,
notificationKey = key,
+ appName = appName,
promotedContent = promotedContent,
)
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 9b6f205fba72..8fa82cad5c32 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -58,6 +58,8 @@ class FakeMobileIconsInteractor(
override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(DEFAULT_DATA_SUB_ID)
+ override val activeMobileDataSubscriptionId: MutableStateFlow<Int?> = MutableStateFlow(null)
+
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
new file mode 100644
index 000000000000..880ba5eee5d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.stackedMobileIconViewModel: StackedMobileIconViewModel by
+ Kosmos.Fixture { StackedMobileIconViewModel(mobileIconsViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
index 06af32e69b75..b23ccbfbd93f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
@@ -14,12 +14,15 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.util.time
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.currentTime
var Kosmos.systemClock by
@@ -27,6 +30,7 @@ var Kosmos.systemClock by
mock {
whenever(elapsedRealtime()).thenAnswer { testScope.currentTime }
whenever(uptimeMillis()).thenAnswer { testScope.currentTime }
+ whenever(currentTimeMillis()).thenAnswer { testScope.currentTime }
}
}
diff --git a/packages/Vcn/TEST_MAPPING b/packages/Vcn/TEST_MAPPING
index 9722a838ab8e..9ca5304eb8d9 100644
--- a/packages/Vcn/TEST_MAPPING
+++ b/packages/Vcn/TEST_MAPPING
@@ -14,5 +14,13 @@
{
"name": "CtsVcnTestCases"
}
+ ],
+ "tethering-mainline-presubmit": [
+ {
+ "name": "FrameworksVcnTests"
+ },
+ {
+ "name": "CtsVcnTestCases"
+ }
]
} \ No newline at end of file
diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp
index edb22c0e7aa0..c53123359872 100644
--- a/packages/Vcn/framework-b/Android.bp
+++ b/packages/Vcn/framework-b/Android.bp
@@ -77,8 +77,7 @@ framework_connectivity_b_defaults_soong_config {
],
soong_config_variables: {
is_vcn_in_mainline: {
- //TODO: b/380155299 Make it Baklava when aidl tool can understand it
- min_sdk_version: "current",
+ min_sdk_version: "36",
static_libs: ["android.net.vcn.flags-aconfig-java"],
apex_available: ["com.android.tethering"],
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 4fa0d506f09e..aa82df493f84 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -101,8 +101,15 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
@Override
- public void toggleAutoclickPause() {
- // TODO(b/388872274): allows users to pause the autoclick.
+ public void toggleAutoclickPause(boolean paused) {
+ if (paused) {
+ if (mClickScheduler != null) {
+ mClickScheduler.cancel();
+ }
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.cancel();
+ }
+ }
}
};
@@ -133,7 +140,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
mAutoclickIndicatorScheduler);
}
- handleMouseMotion(event, policyFlags);
+ if (!isPaused()) {
+ handleMouseMotion(event, policyFlags);
+ }
} else if (mClickScheduler != null) {
mClickScheduler.cancel();
}
@@ -216,6 +225,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
}
+ private boolean isPaused() {
+ // TODO (b/397460424): Unpause when hovering over panel.
+ return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused();
+ }
+
/**
* Observes autoclick setting values, and updates ClickScheduler delay and indicator size
* whenever the setting value changes.
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index 23c5cc4111f6..ba3e3d14b9c6 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -79,11 +79,20 @@ public class AutoclickTypePanel {
// An interface exposed to {@link AutoclickController) to handle different actions on the panel,
// including changing autoclick type, pausing/resuming autoclick.
public interface ClickPanelControllerInterface {
- // Allows users to change a different autoclick type.
+ /**
+ * Allows users to change a different autoclick type.
+ *
+ * @param clickType The new autoclick type to use. Should be one of the values defined in
+ * {@link AutoclickType}.
+ */
void handleAutoclickTypeChange(@AutoclickType int clickType);
- // Allows users to pause/resume the autoclick.
- void toggleAutoclickPause();
+ /**
+ * Allows users to pause or resume autoclick.
+ *
+ * @param paused {@code true} to pause autoclick, {@code false} to resume.
+ */
+ void toggleAutoclickPause(boolean paused);
}
private final Context mContext;
@@ -211,6 +220,10 @@ public class AutoclickTypePanel {
mWindowManager.removeView(mContentView);
}
+ public boolean isPaused() {
+ return mPaused;
+ }
+
/** Toggles the panel expanded or collapsed state. */
private void togglePanelExpansion(@AutoclickType int clickType) {
final LinearLayout button = getButtonFromClickType(clickType);
@@ -234,6 +247,7 @@ public class AutoclickTypePanel {
private void togglePause() {
mPaused = !mPaused;
+ mClickPanelController.toggleAutoclickPause(mPaused);
ImageButton imageButton = (ImageButton) mPauseButton.getChildAt(/* index= */ 0);
if (mPaused) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 05301fdd8385..4f56483f487e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,6 +31,8 @@ import static android.os.UserHandle.getCallingUserId;
import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
+import static com.android.server.companion.association.DisassociationProcessor.REASON_API;
+import static com.android.server.companion.association.DisassociationProcessor.REASON_PKG_DATA_CLEARED;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
@@ -250,7 +252,7 @@ public class CompanionDeviceManagerService extends SystemService {
+ packageName + "]. Cleaning up CDM data...");
for (AssociationInfo association : associationsForPackage) {
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(association.getId(), REASON_PKG_DATA_CLEARED);
}
mCompanionAppBinder.onPackageChanged(userId);
@@ -426,7 +428,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void disassociate(int associationId) {
- mDisassociationProcessor.disassociate(associationId);
+ mDisassociationProcessor.disassociate(associationId, REASON_API);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index e7d1460aa66a..c5ac7c31b5c3 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,6 +18,8 @@ package com.android.server.companion;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static com.android.server.companion.association.DisassociationProcessor.REASON_SHELL;
+
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
import android.companion.Flags;
@@ -122,7 +124,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
if (association == null) {
out.println("Association doesn't exist.");
} else {
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(association.getId(), REASON_SHELL);
}
}
break;
@@ -132,7 +134,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
final List<AssociationInfo> userAssociations =
mAssociationStore.getAssociationsByUser(userId);
for (AssociationInfo association : userAssociations) {
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(association.getId(), REASON_SHELL);
}
}
break;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index f2d019bde703..ce7dcd0fa1d4 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -58,6 +58,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
@@ -164,6 +165,7 @@ public final class AssociationDiskStore {
private static final String FILE_NAME_LEGACY = "companion_device_manager_associations.xml";
private static final String FILE_NAME = "companion_device_manager.xml";
+ private static final String FILE_NAME_LAST_REMOVED_ASSOCIATION = "last_removed_association.txt";
private static final String XML_TAG_STATE = "state";
private static final String XML_TAG_ASSOCIATIONS = "associations";
@@ -268,6 +270,46 @@ public final class AssociationDiskStore {
}
}
+ /**
+ * Read the last removed association from disk.
+ */
+ public String readLastRemovedAssociation(@UserIdInt int userId) {
+ final AtomicFile file = createStorageFileForUser(
+ userId, FILE_NAME_LAST_REMOVED_ASSOCIATION);
+ StringBuilder sb = new StringBuilder();
+ int c;
+ try (FileInputStream fis = file.openRead()) {
+ while ((c = fis.read()) != -1) {
+ sb.append((char) c);
+ }
+ fis.close();
+ return sb.toString();
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "File " + file + " for user=" + userId + " doesn't exist.");
+ return null;
+ } catch (IOException e) {
+ Slog.e(TAG, "Can't read file " + file + " for user=" + userId);
+ return null;
+ }
+ }
+
+ /**
+ * Write the last removed association to disk.
+ */
+ public void writeLastRemovedAssociation(AssociationInfo association, String reason) {
+ Slog.i(TAG, "Writing last removed association=" + association.getId() + " to disk...");
+
+ final AtomicFile file = createStorageFileForUser(
+ association.getUserId(), FILE_NAME_LAST_REMOVED_ASSOCIATION);
+ writeToFileSafely(file, out -> {
+ out.write(String.valueOf(System.currentTimeMillis()).getBytes());
+ out.write(' ');
+ out.write(reason.getBytes());
+ out.write(' ');
+ out.write(association.toString().getBytes());
+ });
+ }
+
@NonNull
private static Associations readAssociationsFromFile(@UserIdInt int userId,
@NonNull AtomicFile file, @NonNull String rootTag) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index 757abd927ac8..f70c434e6b46 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -276,7 +276,7 @@ public class AssociationStore {
/**
* Remove an association.
*/
- public void removeAssociation(int id) {
+ public void removeAssociation(int id, String reason) {
Slog.i(TAG, "Removing association id=[" + id + "]...");
final AssociationInfo association;
@@ -291,6 +291,8 @@ public class AssociationStore {
writeCacheToDisk(association.getUserId());
+ mDiskStore.writeLastRemovedAssociation(association, reason);
+
Slog.i(TAG, "Done removing association.");
}
@@ -525,6 +527,14 @@ public class AssociationStore {
out.append(" ").append(a.toString()).append('\n');
}
}
+
+ out.append("Last Removed Association:\n");
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ String lastRemovedAssociation = mDiskStore.readLastRemovedAssociation(user.id);
+ if (lastRemovedAssociation != null) {
+ out.append(" ").append(lastRemovedAssociation).append('\n');
+ }
+ }
}
private void broadcastChange(@ChangeType int changeType, AssociationInfo association) {
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index 150e8da5f614..248056f32a4f 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -47,6 +47,13 @@ import com.android.server.companion.transport.CompanionTransportManager;
@SuppressLint("LongLogTag")
public class DisassociationProcessor {
+ public static final String REASON_REVOKED = "revoked";
+ public static final String REASON_SELF_IDLE = "self-idle";
+ public static final String REASON_SHELL = "shell";
+ public static final String REASON_LEGACY = "legacy";
+ public static final String REASON_API = "api";
+ public static final String REASON_PKG_DATA_CLEARED = "pkg-data-cleared";
+
private static final String TAG = "CDM_DisassociationProcessor";
private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
@@ -94,7 +101,7 @@ public class DisassociationProcessor {
* Disassociate an association by id.
*/
// TODO: also revoke notification access
- public void disassociate(int id) {
+ public void disassociate(int id, String reason) {
Slog.i(TAG, "Disassociating id=[" + id + "]...");
final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
@@ -126,7 +133,7 @@ public class DisassociationProcessor {
// Association cleanup.
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
- mAssociationStore.removeAssociation(association.getId());
+ mAssociationStore.removeAssociation(association.getId(), reason);
// If role is not in use by other associations, revoke the role.
// Do not need to remove the system role since it was pre-granted by the system.
@@ -151,7 +158,7 @@ public class DisassociationProcessor {
}
/**
- * @deprecated Use {@link #disassociate(int)} instead.
+ * @deprecated Use {@link #disassociate(int, String)} instead.
*/
@Deprecated
public void disassociate(int userId, String packageName, String macAddress) {
@@ -165,7 +172,7 @@ public class DisassociationProcessor {
mAssociationStore.getAssociationWithCallerChecks(association.getId());
- disassociate(association.getId());
+ disassociate(association.getId(), REASON_LEGACY);
}
@SuppressLint("MissingPermission")
@@ -223,7 +230,7 @@ public class DisassociationProcessor {
Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString()
+ "].");
- disassociate(id);
+ disassociate(id, REASON_SELF_IDLE);
}
}
@@ -234,7 +241,7 @@ public class DisassociationProcessor {
*
* Lastly remove the role holder for the revoked associations for the same packages.
*
- * @see #disassociate(int)
+ * @see #disassociate(int, String)
*/
private class OnPackageVisibilityChangeListener implements
ActivityManager.OnUidImportanceListener {
@@ -260,7 +267,7 @@ public class DisassociationProcessor {
int userId = UserHandle.getUserId(uid);
for (AssociationInfo association : mAssociationStore.getRevokedAssociations(userId,
packageName)) {
- disassociate(association.getId());
+ disassociate(association.getId(), REASON_REVOKED);
}
if (mAssociationStore.getRevokedAssociations().isEmpty()) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 139bbae26289..93b4de856463 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -891,6 +891,11 @@ public class VirtualDeviceManagerService extends SystemService {
}
@Override
+ public VirtualDevice getVirtualDevice(int deviceId) {
+ return mImpl.getVirtualDevice(deviceId);
+ }
+
+ @Override
public long getDimDurationMillisForDeviceId(int deviceId) {
VirtualDeviceImpl virtualDevice = getVirtualDeviceForId(deviceId);
return virtualDevice == null ? -1 : virtualDevice.getDimDurationMillis();
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index d8e10f842665..7eb7072520de 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -365,7 +365,7 @@ public class ContextualSearchManagerService extends SystemService {
}
}
final ScreenshotHardwareBuffer shb = mWmInternal.takeContextualSearchScreenshot(
- (Flags.contextualSearchWindowLayer() ? csUid : -1));
+ (Flags.contextualSearchPreventSelfCapture() ? csUid : -1));
final Bitmap bm = shb != null ? shb.asBitmap() : null;
// Now that everything is fetched, putting it in the launchIntent.
if (bm != null) {
@@ -549,7 +549,7 @@ public class ContextualSearchManagerService extends SystemService {
Binder.withCleanCallingIdentity(() -> {
final ScreenshotHardwareBuffer shb =
mWmInternal.takeContextualSearchScreenshot(
- (Flags.contextualSearchWindowLayer() ? callingUid : -1));
+ (Flags.contextualSearchPreventSelfCapture() ? callingUid : -1));
final Bitmap bm = shb != null ? shb.asBitmap() : null;
if (bm != null) {
bundle.putParcelable(ContextualSearchManager.EXTRA_SCREENSHOT,
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index d2a5734f323f..b6fe0ad37078 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -49,7 +49,6 @@ import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
-import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -2443,6 +2442,7 @@ class StorageManagerService extends IStorageManager.Stub
} catch (Installer.InstallerException e) {
Slog.e(TAG, "Failed unmount mirror data", e);
}
+ extendWatchdogTimeout("#unmount might be slow");
mVold.unmount(vol.getId());
mStorageSessionController.onVolumeUnmount(vol.getImmutableVolumeInfo());
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index cce29592d912..125824c3f37e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -618,7 +618,7 @@ public final class ActiveServices {
Slog.i(TAG, " Stopping fg for service " + r);
}
setServiceForegroundInnerLocked(r, 0, null, 0, 0,
- 0);
+ 0, /* systemRequestedTransition= */ true);
}
}
@@ -1839,7 +1839,7 @@ public final class ActiveServices {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType,
- callingUid);
+ callingUid, /* systemRequestedTransition= */ false);
}
} finally {
mAm.mInjector.restoreCallingIdentity(origId);
@@ -2155,7 +2155,7 @@ public final class ActiveServices {
@GuardedBy("mAm")
private void setServiceForegroundInnerLocked(final ServiceRecord r, int id,
Notification notification, int flags, int foregroundServiceType,
- int callingUidIfStart) {
+ int callingUidIfStart, boolean systemRequestedTransition) {
if (id != 0) {
if (notification == null) {
throw new IllegalArgumentException("null notification");
@@ -2800,6 +2800,7 @@ public final class ActiveServices {
// earlier.
r.foregroundServiceType = 0;
r.mFgsNotificationWasDeferred = false;
+ r.systemRequestedFgToBg = systemRequestedTransition;
signalForegroundServiceObserversLocked(r);
resetFgsRestrictionLocked(r);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
@@ -9339,14 +9340,22 @@ public final class ActiveServices {
if (sr.foregroundServiceType
== ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE
&& sr.foregroundId == notificationId) {
- if (DEBUG_FOREGROUND_SERVICE) {
- Slog.d(TAG, "Moving media service to foreground for package "
- + packageName);
+ // check if service is explicitly requested by app to not be in foreground.
+ if (sr.systemRequestedFgToBg) {
+ Slog.d(TAG,
+ "System initiated service transition to foreground "
+ + "for package "
+ + packageName);
+ setServiceForegroundInnerLocked(sr, sr.foregroundId,
+ sr.foregroundNoti, /* flags */ 0,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ /* callingUidStart */ 0, /* systemRequestedTransition */ true);
+ } else {
+ Slog.d(TAG,
+ "Ignoring system initiated foreground service transition for "
+ + "package"
+ + packageName);
}
- setServiceForegroundInnerLocked(sr, sr.foregroundId,
- sr.foregroundNoti, /* flags */ 0,
- ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
- /* callingUidStart */ 0);
}
}
}
@@ -9379,13 +9388,14 @@ public final class ActiveServices {
if (sr.foregroundServiceType
== ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
&& sr.foregroundId == notificationId) {
- if (DEBUG_FOREGROUND_SERVICE) {
- Slog.d(TAG, "Forcing media foreground service to background for package "
- + packageName);
- }
+ Slog.d(TAG,
+ "System initiated transition of foreground service(type:media) to bg "
+ + "for package"
+ + packageName);
setServiceForegroundInnerLocked(sr, /* id */ 0,
/* notification */ null, /* flags */ 0,
- /* foregroundServiceType */ 0, /* callingUidStart */ 0);
+ /* foregroundServiceType */ 0, /* callingUidStart */ 0,
+ /* systemRequestedTransition */ true);
}
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index b677297dfef2..4bfee1d8398f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1464,7 +1464,10 @@ public class CachedAppOptimizer {
void onProcessFrozen(ProcessRecord frozenProc) {
if (useCompaction()) {
synchronized (mProcLock) {
- compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
+ // only full-compact if process is cached
+ if (frozenProc.mState.getSetAdj() >= mCompactThrottleMinOomAdj) {
+ compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
+ }
}
}
frozenProc.onProcessFrozen();
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index cc6fabc8fd67..4b6d6bc955cc 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -68,7 +68,7 @@ per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNE
per-file ActivityManager* = file:/ACTIVITY_SECURITY_OWNERS
# Aconfig Flags
-per-file flags.aconfig = yamasani@google.com, bills@google.com, nalini@google.com
+per-file flags.aconfig = yamasani@google.com, nalini@google.com
# Londoners
michaelwr@google.com #{LAST_RESORT_SUGGESTION}
@@ -77,4 +77,4 @@ narayan@google.com #{LAST_RESORT_SUGGESTION}
# Default
yamasani@google.com
hackbod@google.com #{LAST_RESORT_SUGGESTION}
-omakoto@google.com #{LAST_RESORT_SUGGESTION} \ No newline at end of file
+omakoto@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 61c5501a7b5a..13d367a95942 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -446,6 +446,8 @@ public class OomAdjuster {
private static final int CACHING_UI_SERVICE_CLIENT_ADJ_THRESHOLD =
Flags.raiseBoundUiServiceThreshold() ? SERVICE_ADJ : PERCEPTIBLE_APP_ADJ;
+ static final long PERCEPTIBLE_TASK_TIMEOUT_MILLIS = 5 * 60 * 1000;
+
@VisibleForTesting
public static class Injector {
boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -1847,7 +1849,7 @@ public class OomAdjuster {
mHasVisibleActivities = false;
}
- void onOtherActivity() {
+ void onOtherActivity(long perceptibleTaskStoppedTimeMillis) {
if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
procState = PROCESS_STATE_CACHED_ACTIVITY;
mAdjType = "cch-act";
@@ -1856,6 +1858,28 @@ public class OomAdjuster {
"Raise procstate to cached activity: " + app);
}
}
+ if (Flags.perceptibleTasks() && adj > PERCEPTIBLE_MEDIUM_APP_ADJ) {
+ if (perceptibleTaskStoppedTimeMillis >= 0) {
+ final long now = mInjector.getUptimeMillis();
+ if (now - perceptibleTaskStoppedTimeMillis < PERCEPTIBLE_TASK_TIMEOUT_MILLIS) {
+ adj = PERCEPTIBLE_MEDIUM_APP_ADJ;
+ mAdjType = "perceptible-act";
+ if (procState > PROCESS_STATE_IMPORTANT_BACKGROUND) {
+ procState = PROCESS_STATE_IMPORTANT_BACKGROUND;
+ }
+
+ maybeSetProcessFollowUpUpdateLocked(app,
+ perceptibleTaskStoppedTimeMillis + PERCEPTIBLE_TASK_TIMEOUT_MILLIS,
+ now);
+ } else if (adj > PREVIOUS_APP_ADJ) {
+ adj = PREVIOUS_APP_ADJ;
+ mAdjType = "stale-perceptible-act";
+ if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+ procState = PROCESS_STATE_LAST_ACTIVITY;
+ }
+ }
+ }
+ }
mHasVisibleActivities = false;
}
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index b0f808b39053..25175e6bee5f 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -1120,7 +1120,8 @@ final class ProcessStateRecord {
} else if ((flags & ACTIVITY_STATE_FLAG_IS_STOPPING) != 0) {
callback.onStoppingActivity((flags & ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) != 0);
} else {
- callback.onOtherActivity();
+ final long ts = mApp.getWindowProcessController().getPerceptibleTaskStoppedTimeMillis();
+ callback.onOtherActivity(ts);
}
mCachedAdj = callback.adj;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index ca34a13c55b1..f443e4455734 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -158,6 +158,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
boolean fgWaiting; // is a timeout for going foreground already scheduled?
boolean isNotAppComponentUsage; // is service binding not considered component/package usage?
boolean isForeground; // is service currently in foreground mode?
+ boolean systemRequestedFgToBg; // system requested service to transition to background.
boolean inSharedIsolatedProcess; // is the service in a shared isolated process
int foregroundId; // Notification ID of last foreground req.
Notification foregroundNoti; // Notification record of foreground state.
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e0fbaf43ea43..18f3500b2d56 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -4059,7 +4059,7 @@ class UserController implements Handler.Callback {
synchronized (mUserSwitchingDialogLock) {
dismissUserSwitchingDialog(null);
mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
- switchingFromSystemUserMessage, switchingToSystemUserMessage);
+ mHandler, switchingFromSystemUserMessage, switchingToSystemUserMessage);
mUserSwitchingDialog.show(onShown);
}
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index d1fcb9d1ca37..223e0b79ec0b 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -34,7 +34,6 @@ import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.Looper;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -72,7 +71,7 @@ class UserSwitchingDialog extends Dialog {
// Time to wait for the onAnimationEnd() callbacks before moving on
private static final int ANIMATION_TIMEOUT_MS = 1000;
- private final Handler mHandler = new Handler(Looper.myLooper());
+ private final Handler mHandler;
protected final UserInfo mOldUser;
protected final UserInfo mNewUser;
@@ -81,13 +80,14 @@ class UserSwitchingDialog extends Dialog {
protected final Context mContext;
private final int mTraceCookie;
- UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser,
+ UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, Handler handler,
String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
mContext = context;
mOldUser = oldUser;
mNewUser = newUser;
+ mHandler = handler;
mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
mDisableAnimations = SystemProperties.getBoolean(
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 27c384a22fb6..c8fedf3d1765 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -293,6 +293,13 @@ flag {
}
flag {
+ name: "perceptible_tasks"
+ namespace: "system_performance"
+ description: "Boost the oom_score_adj of activities in perceptible tasks"
+ bug: "370890207"
+}
+
+flag {
name: "expedite_activity_launch_on_cold_start"
namespace: "system_performance"
description: "Notify ActivityTaskManager of cold starts early to fix app launch behavior."
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index d412277d2605..f5284a3ed589 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -211,4 +211,20 @@ public abstract class VirtualDeviceManagerInternal {
*/
public abstract @NonNull VirtualDeviceManager.VirtualDevice createVirtualDevice(
@NonNull VirtualDeviceParams params);
+
+ /**
+ * Returns the details of the virtual device with the given ID, if any.
+ *
+ * <p>The returned object is a read-only representation of the virtual device that expose its
+ * properties.</p>
+ *
+ * <p>Note that if the virtual device is closed and becomes invalid, the returned object will
+ * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real
+ * time updates of the availability of virtual devices.</p>
+ *
+ * @return the virtual device with the requested ID, or {@code null} if no such device exists or
+ * it has already been closed.
+ */
+ @Nullable
+ public abstract VirtualDevice getVirtualDevice(int deviceId);
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 88f5c81231b8..c41b8db1ce75 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -333,7 +333,7 @@ public class AutomaticBrightnessController {
int ambientLightHorizonLong, float userLux, float userNits,
DisplayManagerFlags displayManagerFlags) {
mInjector = injector;
- mClock = injector.createClock(displayManagerFlags.offloadControlsDozeAutoBrightness());
+ mClock = injector.createClock();
mContext = context;
mCallbacks = callbacks;
mSensorManager = sensorManager;
@@ -1402,8 +1402,7 @@ public class AutomaticBrightnessController {
public void onSensorChanged(SensorEvent event) {
if (mLightSensorEnabled) {
// The time received from the sensor is in nano seconds, hence changing it to ms
- final long time = (mDisplayManagerFlags.offloadControlsDozeAutoBrightness())
- ? TimeUnit.NANOSECONDS.toMillis(event.timestamp) : mClock.uptimeMillis();
+ final long time = TimeUnit.NANOSECONDS.toMillis(event.timestamp);
final float lux = event.values[0];
handleLightSensorEvent(time, lux);
}
@@ -1616,20 +1615,13 @@ public class AutomaticBrightnessController {
}
private static class RealClock implements Clock {
- private final boolean mOffloadControlsDozeBrightness;
-
- RealClock(boolean offloadControlsDozeBrightness) {
- mOffloadControlsDozeBrightness = offloadControlsDozeBrightness;
- }
-
@Override
public long uptimeMillis() {
return SystemClock.uptimeMillis();
}
public long getSensorEventScaleTime() {
- return (mOffloadControlsDozeBrightness)
- ? SystemClock.elapsedRealtime() : uptimeMillis();
+ return SystemClock.elapsedRealtime();
}
}
@@ -1638,8 +1630,8 @@ public class AutomaticBrightnessController {
return BackgroundThread.getHandler();
}
- Clock createClock(boolean offloadControlsDozeBrightness) {
- return new RealClock(offloadControlsDozeBrightness);
+ Clock createClock() {
+ return new RealClock();
}
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 872f33484951..f4daf8761e9b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -890,10 +890,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// still need to let WindowManager know so it can update its own internal state for
// things like display cutouts.
display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
- if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED
- | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
+ if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo,
+ /* compareOnlyBasicChanges */ true)) {
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED;
}
+ logicalDisplayEventMask
+ |= updateAndGetMaskForDisplayPropertyChanges(mTempNonOverrideDisplayInfo);
}
mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED);
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 2c6f37448735..6510441ba28f 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -291,8 +291,7 @@ public class DisplayBrightnessStrategySelector {
void setAllowAutoBrightnessWhileDozing(
DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
mAllowAutoBrightnessWhileDozing = mAllowAutoBrightnessWhileDozingConfig;
- if (mDisplayManagerFlags.offloadControlsDozeAutoBrightness()
- && mDisplayManagerFlags.isDisplayOffloadEnabled()
+ if (mDisplayManagerFlags.isDisplayOffloadEnabled()
&& displayOffloadSession != null) {
mAllowAutoBrightnessWhileDozing &= displayOffloadSession.allowAutoBrightnessInDoze();
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index e4b595ab7c55..7cc178d5ff6c 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -156,11 +156,6 @@ public class DisplayManagerFlags {
Flags.FLAG_DOZE_BRIGHTNESS_FLOAT,
Flags::dozeBrightnessFloat);
- private final FlagState mOffloadControlsDozeAutoBrightness = new FlagState(
- Flags.FLAG_OFFLOAD_CONTROLS_DOZE_AUTO_BRIGHTNESS,
- Flags::offloadControlsDozeAutoBrightness
- );
-
private final FlagState mPeakRefreshRatePhysicalLimit = new FlagState(
Flags.FLAG_ENABLE_PEAK_REFRESH_RATE_PHYSICAL_LIMIT,
Flags::enablePeakRefreshRatePhysicalLimit
@@ -285,6 +280,11 @@ public class DisplayManagerFlags {
Flags::committedStateSeparateEvent
);
+ private final FlagState mDelayImplicitRrRegistrationUntilRrAccessed = new FlagState(
+ Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED,
+ Flags::delayImplicitRrRegistrationUntilRrAccessed
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -440,13 +440,6 @@ public class DisplayManagerFlags {
return mDozeBrightnessFloat.isEnabled();
}
- /**
- * @return Whether DisplayOffload should control auto-brightness in doze
- */
- public boolean offloadControlsDozeAutoBrightness() {
- return mOffloadControlsDozeAutoBrightness.isEnabled();
- }
-
public boolean isPeakRefreshRatePhysicalLimitEnabled() {
return mPeakRefreshRatePhysicalLimit.isEnabled();
}
@@ -598,7 +591,6 @@ public class DisplayManagerFlags {
return mFramerateOverrideTriggersRrCallbacks.isEnabled();
}
-
/**
* @return {@code true} if the flag for sending refresh rate events only for the apps in
* foreground is enabled
@@ -616,6 +608,13 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if the flag for only explicit subscription for RR changes is enabled
+ */
+ public boolean isDelayImplicitRrRegistrationUntilRrAccessedEnabled() {
+ return mDelayImplicitRrRegistrationUntilRrAccessed.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -647,7 +646,6 @@ public class DisplayManagerFlags {
pw.println(" " + mResolutionBackupRestore);
pw.println(" " + mUseFusionProxSensor);
pw.println(" " + mDozeBrightnessFloat);
- pw.println(" " + mOffloadControlsDozeAutoBrightness);
pw.println(" " + mPeakRefreshRatePhysicalLimit);
pw.println(" " + mIgnoreAppPreferredRefreshRate);
pw.println(" " + mSynthetic60hzModes);
@@ -673,6 +671,7 @@ public class DisplayManagerFlags {
pw.println(" " + mFramerateOverrideTriggersRrCallbacks);
pw.println(" " + mRefreshRateEventForForegroundApps);
pw.println(" " + mCommittedStateSeparateEvent);
+ pw.println(" " + mDelayImplicitRrRegistrationUntilRrAccessed);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index acdc0e0cf891..a0064a9f5d1d 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -255,17 +255,6 @@ flag {
}
flag {
- name: "offload_controls_doze_auto_brightness"
- namespace: "display_manager"
- description: "Allows the registered DisplayOffloader to control if auto-brightness is used in doze"
- bug: "327392714"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "enable_peak_refresh_rate_physical_limit"
namespace: "display_manager"
description: "Flag for adding physical refresh rate limit if smooth display setting is on "
@@ -485,9 +474,9 @@ flag {
description: "Feature flag to trigger the RR callbacks when framerate overridding happens."
bug: "390113266"
is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -519,3 +508,14 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "delay_implicit_rr_registration_until_rr_accessed"
+ namespace: "display_manager"
+ description: "Feature flag for clients to subscribe to RR changes by either explicitly subscribing for refresh rate changes or request for refresh rate data"
+ bug: "391828526"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7e8bb28b6a37..2af74f620c95 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -569,7 +569,8 @@ public final class DreamManagerService extends SystemService {
}
private void requestDreamInternal() {
- if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) {
+ if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()
+ && !isDozingInternal()) {
return;
}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 977c029f3a29..d71c8a1056d9 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -146,11 +146,6 @@ final class InputGestureManager {
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
),
createKeyGesture(
- KeyEvent.KEYCODE_N,
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
- ),
- createKeyGesture(
KeyEvent.KEYCODE_S,
KeyEvent.META_META_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 508bc2f811e0..dfdd9e54fe2e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3485,7 +3485,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
|| (windowPerceptible != null && windowPerceptible == perceptible)) {
return;
}
- mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
+ mFocusedWindowPerceptible.put(windowToken, perceptible);
updateSystemUiLocked(userId);
}
});
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
index bb487fb52c9f..ebf7e6bed064 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
@@ -1,4 +1,3 @@
aseemk@google.com
bozhu@google.com
dementyev@google.com
-robertberry@google.com
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0fc182f3f1bb..fff812c038e7 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -42,6 +42,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE;
import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
import android.annotation.FlaggedApi;
@@ -286,7 +287,7 @@ public class PreferencesHelper implements RankingConfig {
if (!TAG_RANKING.equals(tag)) return;
final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
- boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
+ boolean upgradeForBubbles = xmlVersion >= XML_VERSION_BUBBLES_UPGRADE;
boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION);
if (mShowReviewPermissionsNotification
&& (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) {
@@ -337,15 +338,19 @@ public class PreferencesHelper implements RankingConfig {
}
boolean skipWarningLogged = false;
boolean skipGroupWarningLogged = false;
- boolean hasSAWPermission = false;
- if (upgradeForBubbles && uid != UNKNOWN_UID) {
- hasSAWPermission = mAppOps.noteOpNoThrow(
- OP_SYSTEM_ALERT_WINDOW, uid, name, null,
- "check-notif-bubble") == AppOpsManager.MODE_ALLOWED;
- }
- int bubblePref = hasSAWPermission
- ? BUBBLE_PREFERENCE_ALL
- : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
+ int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE,
+ DEFAULT_BUBBLE_PREFERENCE);
+ boolean bubbleLocked = (parser.getAttributeInt(null,
+ ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE)
+ != 0;
+ if (!bubbleLocked
+ && upgradeForBubbles
+ && uid != UNKNOWN_UID
+ && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null,
+ "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) {
+ // User hasn't changed bubble pref & the app has SAW, so allow all bubbles.
+ bubblePref = BUBBLE_PREFERENCE_ALL;
+ }
int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
// when data is loaded from disk it's loaded as USER_ALL, but restored data that
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 44d787f790cf..c31c287017c3 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -82,12 +82,10 @@ import android.util.LongSparseLongArray;
import android.util.Slog;
import android.util.SparseBooleanArray;
-import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.infra.AndroidFuture;
-import com.android.internal.policy.AttributeCache;
import com.android.internal.util.IntPair;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
@@ -167,6 +165,7 @@ public final class PermissionPolicyService extends SystemService {
private Context mContext;
private PackageManagerInternal mPackageManagerInternal;
private PermissionManagerServiceInternal mPermissionManagerInternal;
+ private ActivityTaskManagerInternal mActivityTaskManagerInternal;
private NotificationManagerInternal mNotificationManager;
private TelephonyManager mTelephonyManager;
private final KeyguardManager mKeyguardManager;
@@ -189,6 +188,7 @@ public final class PermissionPolicyService extends SystemService {
PackageManagerInternal.class);
mPermissionManagerInternal = LocalServices.getService(
PermissionManagerServiceInternal.class);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -1154,7 +1154,7 @@ public final class PermissionPolicyService extends SystemService {
activityInfo.packageName, info.getCallingPackage(),
info.getIntent(), info.getCheckedOptions(), activityInfo.name,
true)
- || isNoDisplayActivity(activityInfo)) {
+ || isNoDisplayActivity(activityInfo, info.getUserId())) {
return;
}
UserHandle user = UserHandle.of(taskInfo.userId);
@@ -1170,9 +1170,7 @@ public final class PermissionPolicyService extends SystemService {
};
private void onActivityManagerReady() {
- ActivityTaskManagerInternal atm =
- LocalServices.getService(ActivityTaskManagerInternal.class);
- atm.registerActivityStartInterceptor(
+ mActivityTaskManagerInternal.registerActivityStartInterceptor(
ActivityInterceptorCallback.PERMISSION_POLICY_ORDERED_ID,
mActivityInterceptorCallback);
}
@@ -1227,20 +1225,14 @@ public final class PermissionPolicyService extends SystemService {
null, activityName, false);
}
- private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo) {
+ private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo, int userId) {
final int themeResource = aInfo.getThemeResource();
if (themeResource == Resources.ID_NULL) {
return false;
}
- boolean noDisplay = false;
- final AttributeCache.Entry ent = AttributeCache.instance()
- .get(aInfo.packageName, themeResource, R.styleable.Window, 0);
- if (ent != null) {
- noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
- }
-
- return noDisplay;
+ return mActivityTaskManagerInternal.isNoDisplay(aInfo.packageName, themeResource,
+ userId);
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 76c5240ab623..980fb155999e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1163,6 +1163,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ private boolean shouldShowHub() {
+ final boolean hubEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ 1, mCurrentUserId) == 1;
+
+ return mUserManagerInternal != null && mUserManagerInternal.isUserUnlocked(mCurrentUserId)
+ && hubEnabled && mDreamManagerInternal.dreamConditionActive();
+ }
+
@VisibleForTesting
void powerPress(long eventTime, int count, int displayId) {
// SideFPS still needs to know about suppressed power buttons, in case it needs to block
@@ -1251,7 +1260,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED,
1, mCurrentUserId) == 1;
- if (mDreamManagerInternal.isDreaming() || isKeyguardShowing()) {
+ if ((mDreamManagerInternal != null && mDreamManagerInternal.isDreaming())
+ || isKeyguardShowing()) {
// If the device is already dreaming or on keyguard, go to sleep.
sleepDefaultDisplayFromPowerButton(eventTime, 0);
break;
@@ -1261,9 +1271,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// show hub.
boolean keyguardAvailable = !mLockPatternUtils.isLockScreenDisabled(
mCurrentUserId);
- if (mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled
- && keyguardAvailable && mDreamManagerInternal.dreamConditionActive()) {
- // If the hub can be launched, send a message to keyguard.
+ if (shouldShowHub() && keyguardAvailable) {
+ // If the hub can be launched, send a message to keyguard. We do not know if
+ // the hub is already running or not, keyguard handles turning screen off if
+ // it is.
Bundle options = new Bundle();
options.putBoolean(EXTRA_TRIGGER_HUB, true);
lockNow(options);
@@ -1324,14 +1335,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
* @param isScreenOn Whether the screen is currently on.
* @param noDreamAction The action to perform if dreaming is not possible.
*/
- private void attemptToDreamFromShortPowerButtonPress(
+ private boolean attemptToDreamFromShortPowerButtonPress(
boolean isScreenOn, Runnable noDreamAction) {
if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP
&& mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP) {
// If the power button behavior isn't one that should be able to trigger the dream, give
// up.
noDreamAction.run();
- return;
+ return false;
}
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
@@ -1339,7 +1350,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Slog.d(TAG, "Can't start dreaming when attempting to dream from short power"
+ " press (isScreenOn=" + isScreenOn + ")");
noDreamAction.run();
- return;
+ return false;
}
synchronized (mLock) {
@@ -1350,6 +1361,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
dreamManagerInternal.requestDream();
+
+ return true;
}
/**
@@ -2327,6 +2340,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
WindowWakeUpPolicy getWindowWakeUpPolicy() {
return new WindowWakeUpPolicy(mContext);
}
+
+ DreamManagerInternal getDreamManagerInternal() {
+ return LocalServices.getService(DreamManagerInternal.class);
+ }
}
/** {@inheritDoc} */
@@ -2345,7 +2362,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mInputManager = mContext.getSystemService(InputManager.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
- mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
+ mDreamManagerInternal = injector.getDreamManagerInternal();
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mSensorPrivacyManager = mContext.getSystemService(SensorPrivacyManager.class);
@@ -3701,15 +3718,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
break;
case KeyEvent.KEYCODE_N:
if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- sendSystemKeyToStatusBarAsync(event);
- notifyKeyGestureCompleted(event,
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES);
- } else {
- toggleNotificationPanel();
- notifyKeyGestureCompleted(event,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
- }
+ toggleNotificationPanel();
+ notifyKeyGestureCompleted(event,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
return true;
}
break;
@@ -6398,6 +6409,17 @@ public class PhoneWindowManager implements WindowManagerPolicy {
event.getDisplayId(), event.getKeyCode(), "wakeUpFromWakeKey")) {
return;
}
+
+ if (!shouldShowHub()
+ && mShortPressOnPowerBehavior == SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP
+ && event.getKeyCode() == KEYCODE_POWER
+ && attemptToDreamFromShortPowerButtonPress(false, () -> {})) {
+ // In the case that we should wake to dream and successfully initiate dreaming, do not
+ // continue waking up. Doing so will exit the dream state and cause UI to react
+ // accordingly.
+ return;
+ }
+
wakeUpFromWakeKey(
event.getEventTime(),
event.getKeyCode(),
diff --git a/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java
index 6f6a7ff5064a..ca2237562fe1 100644
--- a/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java
@@ -29,7 +29,8 @@ import com.android.internal.os.PowerStats;
public class ScreenPowerStatsLayout extends PowerStatsLayout {
private static final String EXTRA_DEVICE_SCREEN_COUNT = "dsc";
private static final String EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION = "dsd";
- private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS = "dbd";
+ private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS = "dbds";
+ private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS_COMPAT = "dbd";
private static final String EXTRA_DEVICE_DOZE_DURATION_POSITION = "ddd";
private static final String EXTRA_DEVICE_DOZE_POWER_POSITION = "ddp";
private static final String EXTRA_UID_FOREGROUND_DURATION = "uf";
@@ -55,8 +56,14 @@ public class ScreenPowerStatsLayout extends PowerStatsLayout {
PersistableBundle extras = descriptor.extras;
mDisplayCount = extras.getInt(EXTRA_DEVICE_SCREEN_COUNT, 1);
mDeviceScreenOnDurationPosition = extras.getInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION);
- mDeviceBrightnessDurationPositions = extras.getIntArray(
+ mDeviceBrightnessDurationPositions = getIntArray(extras,
EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS);
+ if (mDeviceBrightnessDurationPositions == null) {
+ // We should never put arrays in PowerStats.Descriptor because of the performance of
+ // .equals
+ mDeviceBrightnessDurationPositions = extras.getIntArray(
+ EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS_COMPAT);
+ }
mDeviceScreenDozeDurationPosition = extras.getInt(EXTRA_DEVICE_DOZE_DURATION_POSITION);
mDeviceScreenDozePowerPosition = extras.getInt(EXTRA_DEVICE_DOZE_POWER_POSITION);
mUidTopActivityTimePosition = extras.getInt(EXTRA_UID_FOREGROUND_DURATION);
@@ -67,7 +74,7 @@ public class ScreenPowerStatsLayout extends PowerStatsLayout {
super.toExtras(extras);
extras.putInt(EXTRA_DEVICE_SCREEN_COUNT, mDisplayCount);
extras.putInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION, mDeviceScreenOnDurationPosition);
- extras.putIntArray(EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS,
+ putIntArray(extras, EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS,
mDeviceBrightnessDurationPositions);
extras.putInt(EXTRA_DEVICE_DOZE_DURATION_POSITION, mDeviceScreenDozeDurationPosition);
extras.putInt(EXTRA_DEVICE_DOZE_POWER_POSITION, mDeviceScreenDozePowerPosition);
diff --git a/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java
index e8df3ddfe5e8..c382534ac433 100644
--- a/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java
@@ -27,8 +27,10 @@ import java.util.Map;
public class SensorPowerStatsLayout extends PowerStatsLayout {
private static final String TAG = "SensorPowerStatsLayout";
- private static final String EXTRA_DEVICE_SENSOR_HANDLES = "dsh";
+ private static final String EXTRA_DEVICE_SENSOR_HANDLES = "dshs";
+ private static final String EXTRA_DEVICE_SENSOR_HANDLES_COMPAT = "dsh";
private static final String EXTRA_UID_SENSOR_POSITIONS = "usp";
+ private static final String EXTRA_UID_SENSOR_POSITIONS_COMPAT = "usps";
private final SparseIntArray mSensorPositions = new SparseIntArray();
@@ -47,8 +49,14 @@ public class SensorPowerStatsLayout extends PowerStatsLayout {
super(descriptor);
PersistableBundle extras = descriptor.extras;
- int[] handlers = extras.getIntArray(EXTRA_DEVICE_SENSOR_HANDLES);
- int[] uidDurationPositions = extras.getIntArray(EXTRA_UID_SENSOR_POSITIONS);
+ int[] handlers = getIntArray(extras, EXTRA_DEVICE_SENSOR_HANDLES);
+ if (handlers == null) {
+ handlers = extras.getIntArray(EXTRA_DEVICE_SENSOR_HANDLES_COMPAT);
+ }
+ int[] uidDurationPositions = getIntArray(extras, EXTRA_UID_SENSOR_POSITIONS);
+ if (uidDurationPositions == null) {
+ uidDurationPositions = extras.getIntArray(EXTRA_UID_SENSOR_POSITIONS_COMPAT);
+ }
if (handlers != null && uidDurationPositions != null) {
for (int i = 0; i < handlers.length; i++) {
@@ -69,8 +77,8 @@ public class SensorPowerStatsLayout extends PowerStatsLayout {
uidDurationPositions[i] = mSensorPositions.valueAt(i);
}
- extras.putIntArray(EXTRA_DEVICE_SENSOR_HANDLES, handlers);
- extras.putIntArray(EXTRA_UID_SENSOR_POSITIONS, uidDurationPositions);
+ putIntArray(extras, EXTRA_DEVICE_SENSOR_HANDLES, handlers);
+ putIntArray(extras, EXTRA_UID_SENSOR_POSITIONS, uidDurationPositions);
}
private void addUidSensorSection(int handle, String label) {
diff --git a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
index a783d543559f..53894a196d24 100644
--- a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
@@ -25,6 +25,7 @@ import android.os.PersistableBundle;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -347,7 +348,10 @@ class AggregatedPowerStats {
Set<Integer> uids = new HashSet<>();
for (int i = 0; i < mPowerComponentStats.size(); i++) {
- mPowerComponentStats.valueAt(i).collectUids(uids);
+ IntArray activeUids = mPowerComponentStats.valueAt(i).getActiveUids();
+ for (int j = activeUids.size() - 1; j >= 0; j--) {
+ uids.add(activeUids.get(j));
+ }
}
Integer[] allUids = uids.toArray(new Integer[uids.size()]);
diff --git a/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java
index d24ea83540cb..1d359335c6d8 100644
--- a/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/BasePowerStatsProcessor.java
@@ -24,13 +24,12 @@ import static com.android.server.power.stats.processor.AggregatedPowerStatsConfi
import android.os.BatteryConsumer;
import android.os.PersistableBundle;
+import android.util.IntArray;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.BasePowerStatsLayout;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.function.DoubleSupplier;
class BasePowerStatsProcessor extends PowerStatsProcessor {
@@ -125,11 +124,12 @@ class BasePowerStatsProcessor extends PowerStatsProcessor {
mCumulativeDischargeUah = 0;
mCumulativeDischargeDurationMs = 0;
- List<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
-
- long durationMs = timestampMs - mStartTimestamp;
- if (!uids.isEmpty()) {
+ // Note that we are calling `getUids` rather than `getActiveUids`, because this Processor
+ // deals with duration rather than power estimation, so it needs to process *all* known
+ // UIDs, not just the ones that contributed PowerStats
+ IntArray uids = stats.getUids();
+ if (uids.size() != 0) {
+ long durationMs = timestampMs - mStartTimestamp;
for (int i = uids.size() - 1; i >= 0; i--) {
long[] uidStats = new long[sStatsLayout.getUidStatsArrayLength()];
sStatsLayout.setUidUsageDuration(uidStats, durationMs);
diff --git a/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
index 9fe7f3e7a542..c89dddf45609 100644
--- a/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.os.BatteryStats;
import android.os.PersistableBundle;
import android.os.Process;
+import android.util.IntArray;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.UsageBasedPowerEstimator;
@@ -27,7 +28,6 @@ import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -190,13 +190,13 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
}
computeDevicePowerEstimates(stats, mPlan, mEnergyConsumerSupported);
- combineDevicePowerEstimates(stats);
- List<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
-
- computeUidActivityTotals(stats, uids);
- computeUidPowerEstimates(stats, uids);
+ IntArray uids = stats.getActiveUids();
+ if (uids.size() != 0) {
+ combineDevicePowerEstimates(stats);
+ computeUidActivityTotals(stats, uids);
+ computeUidPowerEstimates(stats, uids);
+ }
}
protected void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
@@ -239,8 +239,7 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
}
}
- private void computeUidActivityTotals(PowerComponentAggregatedPowerStats stats,
- List<Integer> uids) {
+ private void computeUidActivityTotals(PowerComponentAggregatedPowerStats stats, IntArray uids) {
for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
Intermediates intermediates =
@@ -259,8 +258,7 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
}
}
- private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
- List<Integer> uids) {
+ private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, IntArray uids) {
for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
Intermediates intermediates =
@@ -276,12 +274,13 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
int uid = uids.get(k);
if (stats.getUidStats(mTmpUidStatsArray, uid,
proportionalEstimate.stateValues)) {
- double power = intermediates.power
- * mStatsLayout.getUidUsageDuration(mTmpUidStatsArray)
- / intermediates.duration;
- mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
- stats.setUidStats(uid, proportionalEstimate.stateValues,
- mTmpUidStatsArray);
+ long duration = mStatsLayout.getUidUsageDuration(mTmpUidStatsArray);
+ if (duration != 0) {
+ double power = intermediates.power * duration / intermediates.duration;
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues,
+ mTmpUidStatsArray);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java
index 4c1a0db02273..c1cd3acf1656 100644
--- a/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java
@@ -16,12 +16,13 @@
package com.android.server.power.stats.processor;
+import android.util.IntArray;
+
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.UsageBasedPowerEstimator;
import com.android.server.power.stats.format.BluetoothPowerStatsLayout;
-import java.util.ArrayList;
import java.util.List;
class BluetoothPowerStatsProcessor extends PowerStatsProcessor {
@@ -118,18 +119,19 @@ class BluetoothPowerStatsProcessor extends PowerStatsProcessor {
combineDeviceStateEstimates();
- ArrayList<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
- if (!uids.isEmpty()) {
- for (int uid : uids) {
- for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
- computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(i));
+ IntArray uids = stats.getActiveUids();
+ if (uids.size() != 0) {
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ int uid = uids.get(i);
+ for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) {
+ computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(j));
}
}
- for (int uid : uids) {
- for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
- computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i));
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ int uid = uids.get(i);
+ for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) {
+ computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(j));
}
}
}
@@ -297,8 +299,10 @@ class BluetoothPowerStatsProcessor extends PowerStatsProcessor {
/ intermediates.txBytes;
}
}
- mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
- stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ if (power != 0) {
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
index 17ceca6e3dc1..ab2489c81449 100644
--- a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
@@ -19,6 +19,7 @@ package com.android.server.power.stats.processor;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.util.ArraySet;
+import android.util.IntArray;
import android.util.Log;
import com.android.internal.os.CpuScalingPolicies;
@@ -27,7 +28,6 @@ import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.CpuPowerStatsLayout;
import com.android.server.power.stats.format.WakelockPowerStatsLayout;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -119,6 +119,8 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
mWakelockDescriptor = null;
+
+ initEnergyConsumerToPowerBracketMaps();
}
/**
@@ -157,9 +159,6 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
if (mPlan == null) {
mPlan = new PowerEstimationPlan(stats.getConfig());
- if (mStatsLayout.getEnergyConsumerCount() != 0) {
- initEnergyConsumerToPowerBracketMaps();
- }
}
Intermediates intermediates = new Intermediates();
@@ -189,12 +188,12 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
estimatePowerByDeviceState(stats, intermediates, wakelockStats);
combineDeviceStateEstimates();
- ArrayList<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
- if (!uids.isEmpty()) {
- for (int uid : uids) {
- for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
- estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(i),
+ IntArray uids = stats.getActiveUids();
+ if (uids.size() != 0) {
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ int uid = uids.get(i);
+ for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) {
+ estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(j),
wakelockStats);
}
}
@@ -255,6 +254,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
*/
private void initEnergyConsumerToPowerBracketMaps() {
int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
+ if (energyConsumerCount == 0) {
+ return;
+ }
+
int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount];
@@ -545,8 +548,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
power = Math.max(0, power - wakelockPowerEstimate);
}
- mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
- stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ if (power != 0) {
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java
index 76adc47cc165..76ea7e841106 100644
--- a/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java
@@ -16,10 +16,11 @@
package com.android.server.power.stats.processor;
+import android.util.IntArray;
+
import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout;
-import java.util.ArrayList;
import java.util.List;
class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor {
@@ -40,10 +41,8 @@ class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor {
computeDevicePowerEstimates(stats);
- List<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
-
- if (!uids.isEmpty()) {
+ IntArray uids = stats.getActiveUids();
+ if (uids.size() != 0) {
computeUidPowerEstimates(stats, uids);
}
}
@@ -62,7 +61,7 @@ class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor {
}
private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
- List<Integer> uids) {
+ IntArray uids) {
for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
List<UidStateProportionalEstimate> proportionalEstimates =
@@ -73,9 +72,12 @@ class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor {
int uid = uids.get(k);
if (stats.getUidStats(mTmpUidStatsArray, uid,
proportionalEstimate.stateValues)) {
- sLayout.setUidPowerEstimate(mTmpUidStatsArray,
- uCtoMah(sLayout.getUidConsumedEnergy(mTmpUidStatsArray, 0)));
- stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ double power = uCtoMah(sLayout.getUidConsumedEnergy(mTmpUidStatsArray, 0));
+ if (power != 0) {
+ sLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues,
+ mTmpUidStatsArray);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
index b4c40de862b4..a544daad82f1 100644
--- a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
@@ -19,6 +19,7 @@ import android.os.BatteryStats;
import android.telephony.CellSignalStrength;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
+import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -29,7 +30,6 @@ import com.android.internal.power.ModemPowerProfile;
import com.android.server.power.stats.UsageBasedPowerEstimator;
import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -198,18 +198,19 @@ class MobileRadioPowerStatsProcessor extends PowerStatsProcessor {
combineDeviceStateEstimates();
- ArrayList<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
- if (!uids.isEmpty()) {
- for (int uid : uids) {
- for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
- computeUidRxTxTotals(stats, uid, mPlan.uidStateEstimates.get(i));
+ IntArray uids = stats.getActiveUids();
+ if (uids.size() != 0) {
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ int uid = uids.get(i);
+ for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) {
+ computeUidRxTxTotals(stats, uid, mPlan.uidStateEstimates.get(j));
}
}
- for (int uid : uids) {
- for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
- computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i));
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ int uid = uids.get(i);
+ for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) {
+ computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(j));
}
}
}
@@ -382,8 +383,10 @@ class MobileRadioPowerStatsProcessor extends PowerStatsProcessor {
/ intermediates.txPackets;
}
- mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
- stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ if (power != 0) {
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ }
if (DEBUG) {
Slog.d(TAG, "UID: " + uid
diff --git a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
index 28474a554b38..69325757c79d 100644
--- a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
@@ -16,6 +16,7 @@
package com.android.server.power.stats.processor;
+import android.annotation.Nullable;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -333,9 +334,9 @@ class MultiStateStats {
/**
* Adds the delta to the metrics. The number of values must correspond to the dimension count
- * supplied to the Factory constructor
+ * supplied to the Factory constructor. Null values is equivalent to an array of zeros.
*/
- void increment(long[] values, long timestampMs) {
+ void increment(@Nullable long[] values, long timestampMs) {
mCounter.incrementValues(values, timestampMs);
mTracking = true;
}
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
index d4f8fd92fc6c..f9b9da9171cb 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.os.BatteryStats;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -34,7 +35,6 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
-import java.util.Collection;
import java.util.function.IntConsumer;
/**
@@ -72,11 +72,11 @@ class PowerComponentAggregatedPowerStats {
private MultiStateStats mDeviceStats;
private final SparseArray<MultiStateStats> mStateStats = new SparseArray<>();
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
- private long[] mZeroArray;
private static class UidStats {
public int[] states;
public MultiStateStats stats;
+ public boolean hasPowerStats;
public boolean updated;
}
@@ -200,6 +200,7 @@ class PowerComponentAggregatedPowerStats {
if (uidStats.stats == null) {
createUidStats(uidStats, mPowerStatsTimestamp);
}
+ uidStats.hasPowerStats = true;
uidStats.stats.setStats(states, values);
}
@@ -240,6 +241,7 @@ class PowerComponentAggregatedPowerStats {
}
uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs);
uidStats.updated = true;
+ uidStats.hasPowerStats = true;
}
// For UIDs not mentioned in the PowerStats object, we must assume a 0 increment.
@@ -248,11 +250,8 @@ class PowerComponentAggregatedPowerStats {
for (int i = mUidStats.size() - 1; i >= 0; i--) {
PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
if (!uidStats.updated && uidStats.stats != null) {
- if (mZeroArray == null
- || mZeroArray.length != mPowerStatsDescriptor.uidStatsArrayLength) {
- mZeroArray = new long[mPowerStatsDescriptor.uidStatsArrayLength];
- }
- uidStats.stats.increment(mZeroArray, timestampMs);
+ // Null stands for an array of zeros
+ uidStats.stats.increment(null, timestampMs);
}
uidStats.updated = false;
}
@@ -267,6 +266,7 @@ class PowerComponentAggregatedPowerStats {
mStateStats.clear();
for (int i = mUidStats.size() - 1; i >= 0; i--) {
mUidStats.valueAt(i).stats = null;
+ mUidStats.valueAt(i).hasPowerStats = false;
}
}
@@ -290,12 +290,26 @@ class PowerComponentAggregatedPowerStats {
return uidStats;
}
- void collectUids(Collection<Integer> uids) {
+ IntArray getUids() {
+ IntArray uids = new IntArray(mUidStats.size());
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ UidStats uidStats = mUidStats.valueAt(i);
+ if (uidStats.stats != null) {
+ uids.add(mUidStats.keyAt(i));
+ }
+ }
+ return uids;
+ }
+
+ IntArray getActiveUids() {
+ IntArray uids = new IntArray(mUidStats.size());
for (int i = mUidStats.size() - 1; i >= 0; i--) {
- if (mUidStats.valueAt(i).stats != null) {
+ UidStats uidStats = mUidStats.valueAt(i);
+ if (uidStats.hasPowerStats) {
uids.add(mUidStats.keyAt(i));
}
}
+ return uids;
}
boolean getDeviceStats(long[] outValues, int[] deviceStates) {
@@ -516,6 +530,7 @@ class PowerComponentAggregatedPowerStats {
if (uidStats.stats == null) {
createUidStats(uidStats, UNKNOWN);
}
+ uidStats.hasPowerStats = true;
if (!uidStats.stats.readFromXml(parser)) {
return false;
}
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
index 177d12988a27..634415ece806 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
@@ -21,6 +21,7 @@ import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryUsageStats;
import android.os.UidBatteryConsumer;
+import android.util.IntArray;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -31,7 +32,6 @@ import com.android.server.power.stats.PowerStatsStore;
import com.android.server.power.stats.format.BasePowerStatsLayout;
import com.android.server.power.stats.format.PowerStatsLayout;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -180,8 +180,7 @@ class PowerStatsExporter {
}
}
if (layout.isUidPowerAttributionSupported()) {
- populateBatteryConsumers(batteryUsageStatsBuilder,
- powerComponentStats, layout);
+ populateBatteryConsumers(batteryUsageStatsBuilder, powerComponentStats, layout);
}
populateBatteryLevelInfo(batteryUsageStatsBuilder, batteryLevelInfo);
@@ -258,6 +257,11 @@ class PowerStatsExporter {
BatteryUsageStats.Builder batteryUsageStatsBuilder,
PowerComponentAggregatedPowerStats powerComponentStats,
PowerStatsLayout layout) {
+ IntArray uids = powerComponentStats.getUids();
+ if (uids.size() == 0) {
+ return;
+ }
+
AggregatedPowerStatsConfig.PowerComponent powerComponent = powerComponentStats.getConfig();
PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor();
long[] uidStats = new long[descriptor.uidStatsArrayLength];
@@ -273,8 +277,6 @@ class PowerStatsExporter {
breakDownByProcState = false;
}
- ArrayList<Integer> uids = new ArrayList<>();
- powerComponentStats.collectUids(uids);
for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) {
if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) {
if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) {
@@ -303,7 +305,7 @@ class PowerStatsExporter {
private void populateUidBatteryConsumers(
BatteryUsageStats.Builder batteryUsageStatsBuilder,
PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout,
- List<Integer> uids, AggregatedPowerStatsConfig.PowerComponent powerComponent,
+ IntArray uids, AggregatedPowerStatsConfig.PowerComponent powerComponent,
long[] uidStats, boolean breakDownByProcState,
@BatteryConsumer.ScreenState int screenState,
@BatteryConsumer.PowerState int powerState) {
@@ -319,7 +321,8 @@ class PowerStatsExporter {
long[] durationByProcState =
new long[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1];
double powerAllApps = 0;
- for (int uid : uids) {
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ int uid = uids.get(i);
UidBatteryConsumer.Builder builder =
batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid);
diff --git a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
index 8e7498f38fcb..9df3d7eea27b 100644
--- a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
@@ -27,6 +27,7 @@ import static com.android.server.power.stats.processor.AggregatedPowerStatsConfi
import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
import android.os.BatteryStats;
+import android.util.IntArray;
import android.util.Slog;
import com.android.internal.os.PowerProfile;
@@ -34,7 +35,6 @@ import com.android.internal.os.PowerStats;
import com.android.server.power.stats.UsageBasedPowerEstimator;
import com.android.server.power.stats.format.ScreenPowerStatsLayout;
-import java.util.ArrayList;
import java.util.List;
class ScreenPowerStatsProcessor extends PowerStatsProcessor {
@@ -116,10 +116,8 @@ class ScreenPowerStatsProcessor extends PowerStatsProcessor {
computeDevicePowerEstimates(stats);
combineDeviceStateEstimates();
- List<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
-
- if (!uids.isEmpty()) {
+ IntArray uids = stats.getActiveUids();
+ if (uids.size() != 0) {
computeUidPowerEstimates(stats, uids);
}
mPlan.resetIntermediates();
@@ -197,7 +195,7 @@ class ScreenPowerStatsProcessor extends PowerStatsProcessor {
}
private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
- List<Integer> uids) {
+ IntArray uids) {
int[] uidStateValues = new int[stats.getConfig().getUidStateConfig().length];
uidStateValues[STATE_SCREEN] = SCREEN_STATE_ON;
uidStateValues[STATE_PROCESS_STATE] = PROCESS_STATE_UNSPECIFIED;
@@ -232,9 +230,11 @@ class ScreenPowerStatsProcessor extends PowerStatsProcessor {
int uid = uids.get(j);
if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) {
long duration = mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray);
- double power = intermediates.power * duration / totalTopActivityDuration;
- mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
- stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray);
+ if (duration != 0) {
+ double power = intermediates.power * duration / totalTopActivityDuration;
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
index 0bb028bce5af..ba728d36d561 100644
--- a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
@@ -21,6 +21,7 @@ import android.hardware.SensorManager;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.PersistableBundle;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -28,7 +29,6 @@ import com.android.internal.os.PowerStats;
import com.android.server.power.stats.format.PowerStatsLayout;
import com.android.server.power.stats.format.SensorPowerStatsLayout;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
@@ -207,11 +207,11 @@ class SensorPowerStatsProcessor extends PowerStatsProcessor {
mPlan = new PowerEstimationPlan(stats.getConfig());
}
- List<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
-
- computeUidPowerEstimates(stats, uids);
- computeDevicePowerEstimates(stats);
+ IntArray uids = stats.getActiveUids();
+ if (uids.size() != 0) {
+ computeUidPowerEstimates(stats, uids);
+ computeDevicePowerEstimates(stats);
+ }
mPlan.resetIntermediates();
}
@@ -239,9 +239,7 @@ class SensorPowerStatsProcessor extends PowerStatsProcessor {
mLastUpdateTimestamp = timestamp;
}
- private void computeUidPowerEstimates(
- PowerComponentAggregatedPowerStats stats,
- List<Integer> uids) {
+ private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, IntArray uids) {
List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
int[] uidSensorDurationPositions = new int[sensorList.size()];
double[] sensorPower = new double[sensorList.size()];
@@ -292,8 +290,7 @@ class SensorPowerStatsProcessor extends PowerStatsProcessor {
}
}
- private void computeDevicePowerEstimates(
- PowerComponentAggregatedPowerStats stats) {
+ private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
CombinedDeviceStateEstimate estimation =
mPlan.combinedDeviceStateEstimations.get(i);
diff --git a/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java
index 0df01cf7e5d1..8cc0b6eb6150 100644
--- a/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java
@@ -16,6 +16,7 @@
package com.android.server.power.stats.processor;
+import android.util.IntArray;
import android.util.Slog;
import com.android.internal.os.PowerProfile;
@@ -23,7 +24,6 @@ import com.android.internal.os.PowerStats;
import com.android.server.power.stats.UsageBasedPowerEstimator;
import com.android.server.power.stats.format.WifiPowerStatsLayout;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -148,18 +148,19 @@ class WifiPowerStatsProcessor extends PowerStatsProcessor {
combineDeviceStateEstimates();
- ArrayList<Integer> uids = new ArrayList<>();
- stats.collectUids(uids);
- if (!uids.isEmpty()) {
- for (int uid : uids) {
- for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
- computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(i));
+ IntArray uids = stats.getActiveUids();
+ if (uids.size() != 0) {
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ int uid = uids.get(i);
+ for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) {
+ computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(j));
}
}
- for (int uid : uids) {
- for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
- computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i));
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ int uid = uids.get(i);
+ for (int j = 0; j < mPlan.uidStateEstimates.size(); j++) {
+ computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(j));
}
}
}
@@ -374,8 +375,10 @@ class WifiPowerStatsProcessor extends PowerStatsProcessor {
/ intermediates.batchedScanDuration;
}
}
- mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
- stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ if (power != 0) {
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+ }
if (DEBUG) {
Slog.d(TAG, "UID: " + uid
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index 342b864c6473..281aeb68f224 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -156,14 +156,15 @@ public final class StorageSessionController {
StorageUserConnection connection = null;
synchronized (mLock) {
connection = mConnections.get(connectionUserId);
- if (connection != null) {
- Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId);
- connection.notifyVolumeStateChanged(sessionId,
- vol.buildStorageVolume(mContext, vol.getMountUserId(), false));
- } else {
- Slog.w(TAG, "No available storage user connection for userId : "
- + connectionUserId);
- }
+ }
+
+ if (connection != null) {
+ Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId);
+ connection.notifyVolumeStateChanged(sessionId,
+ vol.buildStorageVolume(mContext, vol.getMountUserId(), false));
+ } else {
+ Slog.w(TAG, "No available storage user connection for userId : "
+ + connectionUserId);
}
}
@@ -225,16 +226,18 @@ public final class StorageSessionController {
String sessionId = vol.getId();
int userId = getConnectionUserIdForVolume(vol);
+ StorageUserConnection connection = null;
synchronized (mLock) {
- StorageUserConnection connection = mConnections.get(userId);
- if (connection != null) {
- Slog.i(TAG, "Removed session for vol with id: " + sessionId);
- connection.removeSession(sessionId);
- return connection;
- } else {
- Slog.w(TAG, "Session already removed for vol with id: " + sessionId);
- return null;
- }
+ connection = mConnections.get(userId);
+ }
+
+ if (connection != null) {
+ Slog.i(TAG, "Removed session for vol with id: " + sessionId);
+ connection.removeSession(sessionId);
+ return connection;
+ } else {
+ Slog.w(TAG, "Session already removed for vol with id: " + sessionId);
+ return null;
}
}
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 07d9ad16aca5..da6478bd3395 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -124,6 +124,8 @@ abstract class AbstractVibratorStep extends Step {
Slog.d(VibrationThread.TAG,
"Turning off vibrator " + getVibratorId());
}
+ // Make sure we ignore any pending callback from old vibration commands.
+ conductor.nextVibratorCallbackStepId(getVibratorId());
controller.off();
getVibration().stats.reportVibratorOff();
mPendingVibratorOffDeadline = 0;
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index e495af59a2f9..b3eead109999 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -73,7 +73,8 @@ final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep {
PrimitiveSegment[] primitivesArray =
primitives.toArray(new PrimitiveSegment[primitives.size()]);
- long vibratorOnResult = controller.on(primitivesArray, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(primitivesArray, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportComposePrimitives(vibratorOnResult, primitivesArray);
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
index bb8e6eed5707..7b41457b5016 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
@@ -72,7 +72,8 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
+ controller.getVibratorInfo().getId());
}
PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]);
- long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index e8952fafaf77..d003251ef307 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -71,7 +71,8 @@ final class ComposePwleVibratorStep extends AbstractComposedVibratorStep {
+ controller.getVibratorInfo().getId());
}
RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
- long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index a92ac679b0f4..b2cc1f60ca1d 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -148,7 +148,7 @@ final class ExternalVibrationSession extends Vibration
}
@Override
- public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
// ignored, external control does not expect callbacks from the vibrator
}
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 4b23216258af..88bb181781bf 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -64,7 +64,8 @@ final class PerformPrebakedVibratorStep extends AbstractComposedVibratorStep {
}
VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
- long vibratorOnResult = controller.on(prebaked, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(prebaked, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportPerformEffect(vibratorOnResult, prebaked);
diff --git a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
index 407f3d996798..d4bcc36aee18 100644
--- a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
@@ -49,7 +49,8 @@ final class PerformVendorEffectVibratorStep extends AbstractVibratorStep {
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformVendorEffectVibratorStep");
try {
- long vibratorOnResult = controller.on(effect, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(effect, getVibration().id, stepId);
vibratorOnResult = Math.min(vibratorOnResult, VENDOR_EFFECT_MAX_DURATION_MS);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportPerformVendorEffect(vibratorOnResult);
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 8478e7743183..26b9595e60cd 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
@@ -49,6 +50,10 @@ final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep {
@Override
public boolean acceptVibratorCompleteCallback(int vibratorId) {
+ if (Flags.fixVibrationThreadCallbackHandling()) {
+ // TODO: remove this method once flag removed.
+ return super.acceptVibratorCompleteCallback(vibratorId);
+ }
// Ensure the super method is called and will reset the off timeout and boolean flag.
// This is true if the vibrator was ON and this callback has the same vibratorId.
if (!super.acceptVibratorCompleteCallback(vibratorId)) {
@@ -161,7 +166,8 @@ final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep {
"Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+ duration + "ms");
}
- long vibratorOnResult = controller.on(duration, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(duration, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportVibratorOn(vibratorOnResult);
return vibratorOnResult;
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
index 628221b09d77..309eb8c3b099 100644
--- a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -137,13 +137,13 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec
}
@Override
- public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
if (vibrationId != mVibration.id) {
return;
}
synchronized (mLock) {
if (mConductor != null) {
- mConductor.notifyVibratorComplete(vibratorId);
+ mConductor.notifyVibratorComplete(vibratorId, stepId);
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 1b6ce9dacfa9..bda3d442956b 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -176,14 +176,14 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@Override
public void onCancel() {
- Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
+ Slog.d(TAG, "Session cancellation signal received, aborting vibration session...");
requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
/* isVendorRequest= */ true);
}
@Override
public void binderDied() {
- Slog.d(TAG, "Binder died, cancelling vibration session...");
+ Slog.d(TAG, "Session binder died, aborting vibration session...");
requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
/* isVendorRequest= */ false);
}
@@ -218,19 +218,21 @@ final class VendorVibrationSession extends IVibrationSession.Stub
}
@Override
- public void notifyVibratorCallback(int vibratorId, long vibrationId) {
- // Ignore it, the session vibration playback doesn't depend on HAL timings
+ public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
+ Slog.d(TAG, "Vibration callback received for vibration " + vibrationId + " step " + stepId
+ + " on vibrator " + vibratorId + ", ignoring...");
}
@Override
public void notifySyncedVibratorsCallback(long vibrationId) {
- // Ignore it, the session vibration playback doesn't depend on HAL timings
+ Slog.d(TAG, "Synced vibration callback received for vibration " + vibrationId
+ + ", ignoring...");
}
@Override
public void notifySessionCallback() {
+ Slog.d(TAG, "Session callback received, ending vibration session...");
synchronized (mLock) {
- Slog.d(TAG, "Session callback received, ending vibration session...");
// If end was not requested then the HAL has cancelled the session.
maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
/* isVendorRequest= */ false);
@@ -307,7 +309,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
}
}
if (isAlreadyEnded) {
- // Session already ended, make sure we end it in the HAL.
+ Slog.d(TAG, "Session already ended after starting the HAL, aborting...");
mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true));
}
}
@@ -335,8 +337,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub
public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) {
synchronized (mLock) {
if (mConductor != null) {
- Slog.d(TAG, "Vibration session still dispatching previous vibration,"
- + " new vibration ignored");
+ Slog.d(TAG, "Session still dispatching previous vibration, new vibration "
+ + conductor.getVibration().id + " ignored");
return false;
}
mConductor = conductor;
@@ -345,19 +347,22 @@ final class VendorVibrationSession extends IVibrationSession.Stub
}
private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
+ Slog.d(TAG, "Session end request received with status " + status);
boolean shouldTriggerSessionHook = false;
synchronized (mLock) {
maybeSetEndRequestLocked(status, isVendorRequest);
- if (isStarted()) {
- // Always trigger session hook after it has started, in case new request aborts an
- // already finishing session. Wait for HAL callback before actually ending here.
+ if (!isEnded() && isStarted()) {
+ // Trigger session hook even if it was already triggered, in case a second request
+ // is aborting the ongoing/ending session. This might cause it to end right away.
+ // Wait for HAL callback before setting the end status.
shouldTriggerSessionHook = true;
} else {
- // Session did not start in the HAL, end it right away.
+ // Session not active in the HAL, set end status right away.
maybeSetStatusToRequestedLocked();
}
}
if (shouldTriggerSessionHook) {
+ Slog.d(TAG, "Requesting HAL session end with abort=" + shouldAbort);
mHandler.post(() -> mManagerHooks.endSession(mSessionId, shouldAbort));
}
}
@@ -368,6 +373,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
// End already requested, keep first requested status and time.
return;
}
+ Slog.d(TAG, "Session end request accepted for status " + status);
mEndStatusRequest = status;
mEndedByVendor = isVendorRequest;
mEndTime = System.currentTimeMillis();
@@ -400,6 +406,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
// No end status was requested, nothing to set.
return;
}
+ Slog.d(TAG, "Session end request applied for status " + mEndStatusRequest);
mStatus = mEndStatusRequest;
// Run client callback in separate thread.
final Status endStatus = mStatus;
@@ -407,7 +414,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
try {
mCallback.onFinished(toSessionStatus(endStatus));
} catch (RemoteException e) {
- Slog.e(TAG, "Error notifying vendor session is finishing", e);
+ Slog.e(TAG, "Error notifying vendor session finished", e);
}
});
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index ae95a70e2a4f..23715e392580 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -106,7 +106,7 @@ interface VibrationSession {
* complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
* since its playback might have one or more interactions with the vibrator hardware.
*/
- void notifyVibratorCallback(int vibratorId, long vibrationId);
+ void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId);
/**
* Notify all synced vibrators have completed the last synchronized command during the playback
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 1e20debe156d..36e13224a476 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -30,6 +30,7 @@ import android.os.vibrator.VibrationEffectSegment;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.vibrator.VibrationSession.Status;
@@ -93,6 +94,8 @@ final class VibrationStepConductor {
private final Object mLock = new Object();
@GuardedBy("mLock")
private final IntArray mSignalVibratorsComplete;
+ @GuardedBy("mLock")
+ private final SparseIntArray mSignalVibratorStepIds;
@Nullable
@GuardedBy("mLock")
private Vibration.EndInfo mSignalCancel = null;
@@ -121,6 +124,8 @@ final class VibrationStepConductor {
this.vibratorManagerHooks = vibratorManagerHooks;
this.mSignalVibratorsComplete =
new IntArray(mDeviceAdapter.getAvailableVibratorIds().length);
+ this.mSignalVibratorStepIds =
+ new SparseIntArray(mDeviceAdapter.getAvailableVibratorIds().length);
}
@Nullable
@@ -418,7 +423,7 @@ final class VibrationStepConductor {
* <p>This is a lightweight method intended to be called directly via native callbacks.
* The state update is recorded for processing on the main execution thread (VibrationThread).
*/
- public void notifyVibratorComplete(int vibratorId) {
+ public void notifyVibratorComplete(int vibratorId, long stepId) {
// HAL callbacks may be triggered directly within HAL calls, so these notifications
// could be on the VibrationThread as it calls the HAL, or some other executor later.
// Therefore no thread assertion is made here.
@@ -428,6 +433,14 @@ final class VibrationStepConductor {
}
synchronized (mLock) {
+ if (Flags.fixVibrationThreadCallbackHandling()
+ && mSignalVibratorStepIds.get(vibratorId) != stepId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibrator " + vibratorId + " callback for step=" + stepId
+ + " ignored, current step=" + mSignalVibratorStepIds.get(vibratorId));
+ }
+ return;
+ }
mSignalVibratorsComplete.add(vibratorId);
mLock.notify();
}
@@ -645,6 +658,26 @@ final class VibrationStepConductor {
}
}
+ /**
+ * Updates and returns the next step id value to be used in vibrator commands.
+ *
+ * <p>This new step id will be kept by this conductor to filter out old callbacks that might be
+ * triggered too late by the HAL, preventing them from affecting the ongoing vibration playback.
+ */
+ public int nextVibratorCallbackStepId(int vibratorId) {
+ if (!Flags.fixVibrationThreadCallbackHandling()) {
+ return 0;
+ }
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(true);
+ }
+ synchronized (mLock) {
+ int stepId = mSignalVibratorStepIds.get(vibratorId) + 1;
+ mSignalVibratorStepIds.put(vibratorId, stepId);
+ return stepId;
+ }
+ }
+
private static CombinedVibration.Sequential toSequential(CombinedVibration effect) {
if (effect instanceof CombinedVibration.Sequential) {
return (CombinedVibration.Sequential) effect;
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index acb31ceb4027..ab13b0e88d04 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -72,7 +72,7 @@ final class VibratorController {
public interface OnVibrationCompleteListener {
/** Callback triggered when an active vibration command is complete. */
- void onComplete(int vibratorId, long vibrationId);
+ void onComplete(int vibratorId, long vibrationId, long stepId);
}
/** Representation of the vibrator state based on the interactions through this controller. */
@@ -285,11 +285,11 @@ final class VibratorController {
* @return The positive duration of the vibration started, if successful, zero if the vibrator
* do not support the input or a negative number if the operation failed.
*/
- public long on(long milliseconds, long vibrationId) {
+ public long on(long milliseconds, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on");
try {
synchronized (mLock) {
- long duration = mNativeWrapper.on(milliseconds, vibrationId);
+ long duration = mNativeWrapper.on(milliseconds, vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -310,7 +310,7 @@ final class VibratorController {
* @return The positive duration of the vibration started, if successful, zero if the vibrator
* do not support the input or a negative number if the operation failed.
*/
- public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) {
+ public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)");
synchronized (mLock) {
Parcel vendorData = Parcel.obtain();
@@ -319,7 +319,7 @@ final class VibratorController {
vendorData.setDataPosition(0);
long duration = mNativeWrapper.performVendorEffect(vendorData,
vendorEffect.getEffectStrength(), vendorEffect.getScale(),
- vendorEffect.getAdaptiveScale(), vibrationId);
+ vendorEffect.getAdaptiveScale(), vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -341,12 +341,12 @@ final class VibratorController {
* @return The positive duration of the vibration started, if successful, zero if the vibrator
* do not support the input or a negative number if the operation failed.
*/
- public long on(PrebakedSegment prebaked, long vibrationId) {
+ public long on(PrebakedSegment prebaked, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)");
try {
synchronized (mLock) {
long duration = mNativeWrapper.perform(prebaked.getEffectId(),
- prebaked.getEffectStrength(), vibrationId);
+ prebaked.getEffectStrength(), vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -367,14 +367,14 @@ final class VibratorController {
* @return The positive duration of the vibration started, if successful, zero if the vibrator
* do not support the input or a negative number if the operation failed.
*/
- public long on(PrimitiveSegment[] primitives, long vibrationId) {
+ public long on(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
return 0;
}
synchronized (mLock) {
- long duration = mNativeWrapper.compose(primitives, vibrationId);
+ long duration = mNativeWrapper.compose(primitives, vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -394,7 +394,7 @@ final class VibratorController {
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(RampSegment[] primitives, long vibrationId) {
+ public long on(RampSegment[] primitives, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
@@ -402,7 +402,8 @@ final class VibratorController {
}
synchronized (mLock) {
int braking = mVibratorInfo.getDefaultBraking();
- long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
+ long duration = mNativeWrapper.composePwle(
+ primitives, braking, vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -422,14 +423,14 @@ final class VibratorController {
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(PwlePoint[] pwlePoints, long vibrationId) {
+ public long on(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE v2)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
return 0;
}
synchronized (mLock) {
- long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId);
+ long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -544,26 +545,27 @@ final class VibratorController {
private static native boolean isAvailable(long nativePtr);
- private static native long on(long nativePtr, long milliseconds, long vibrationId);
+ private static native long on(long nativePtr, long milliseconds, long vibrationId,
+ long stepId);
private static native void off(long nativePtr);
private static native void setAmplitude(long nativePtr, float amplitude);
private static native long performEffect(long nativePtr, long effect, long strength,
- long vibrationId);
+ long vibrationId, long stepId);
private static native long performVendorEffect(long nativePtr, Parcel vendorData,
- long strength, float scale, float adaptiveScale, long vibrationId);
+ long strength, float scale, float adaptiveScale, long vibrationId, long stepId);
private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
- long vibrationId);
+ long vibrationId, long stepId);
private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
- int braking, long vibrationId);
+ int braking, long vibrationId, long stepId);
private static native long performPwleV2Effect(long nativePtr, PwlePoint[] effect,
- long vibrationId);
+ long vibrationId, long stepId);
private static native void setExternalControl(long nativePtr, boolean enabled);
@@ -595,8 +597,8 @@ final class VibratorController {
}
/** Turns vibrator on for given time. */
- public long on(long milliseconds, long vibrationId) {
- return on(mNativePtr, milliseconds, vibrationId);
+ public long on(long milliseconds, long vibrationId, long stepId) {
+ return on(mNativePtr, milliseconds, vibrationId, stepId);
}
/** Turns vibrator off. */
@@ -610,30 +612,31 @@ final class VibratorController {
}
/** Turns vibrator on to perform one of the supported effects. */
- public long perform(long effect, long strength, long vibrationId) {
- return performEffect(mNativePtr, effect, strength, vibrationId);
+ public long perform(long effect, long strength, long vibrationId, long stepId) {
+ return performEffect(mNativePtr, effect, strength, vibrationId, stepId);
}
/** Turns vibrator on to perform a vendor-specific effect. */
public long performVendorEffect(Parcel vendorData, long strength, float scale,
- float adaptiveScale, long vibrationId) {
+ float adaptiveScale, long vibrationId, long stepId) {
return performVendorEffect(mNativePtr, vendorData, strength, scale, adaptiveScale,
- vibrationId);
+ vibrationId, stepId);
}
/** Turns vibrator on to perform effect composed of give primitives effect. */
- public long compose(PrimitiveSegment[] primitives, long vibrationId) {
- return performComposedEffect(mNativePtr, primitives, vibrationId);
+ public long compose(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
+ return performComposedEffect(mNativePtr, primitives, vibrationId, stepId);
}
/** Turns vibrator on to perform PWLE effect composed of given primitives. */
- public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
- return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
+ public long composePwle(RampSegment[] primitives, int braking, long vibrationId,
+ long stepId) {
+ return performPwleEffect(mNativePtr, primitives, braking, vibrationId, stepId);
}
/** Turns vibrator on to perform PWLE effect composed of given points. */
- public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
- return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId);
+ public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
+ return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId, stepId);
}
/** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 9de46c878194..3f5fc338ee3b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -61,6 +61,7 @@ import android.os.VibratorInfo;
import android.os.vibrator.Flags;
import android.os.vibrator.IVibrationSessionCallback;
import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.os.vibrator.VibratorInfoFactory;
@@ -786,7 +787,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
synchronized (mLock) {
if (DEBUG) {
- Slog.d(TAG, "Starting session " + session.getSessionId());
+ Slog.d(TAG, "Starting vendor session " + session.getSessionId());
}
Status ignoreStatus = null;
@@ -864,13 +865,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// Session already ended, possibly cancelled by app cancellation signal.
return session.getStatus();
}
- int mode = startAppOpModeLocked(session.getCallerInfo());
+ CallerInfo callerInfo = session.getCallerInfo();
+ int mode = startAppOpModeLocked(callerInfo);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
// Make sure mCurrentVibration is set while triggering the HAL.
mCurrentSession = session;
if (!session.linkToDeath()) {
+ // Shouldn't happen. The method call already logs.
+ finishAppOpModeLocked(callerInfo);
mCurrentSession = null;
return Status.IGNORED_ERROR_TOKEN;
}
@@ -878,14 +882,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.e(TAG, "Error starting session " + sessionId + " on vibrators "
+ Arrays.toString(session.getVibratorIds()));
session.unlinkToDeath();
+ finishAppOpModeLocked(callerInfo);
mCurrentSession = null;
return Status.IGNORED_UNSUPPORTED;
}
session.notifyStart();
return null;
case AppOpsManager.MODE_ERRORED:
- Slog.w(TAG, "Start AppOpsManager operation errored for uid "
- + session.getCallerInfo().uid);
+ Slog.w(TAG, "Start AppOpsManager operation errored for uid " + callerInfo.uid);
return Status.IGNORED_ERROR_APP_OPS;
default:
return Status.IGNORED_APP_OPS;
@@ -1081,11 +1085,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Nullable
private Status startVibrationOnThreadLocked(SingleVibrationSession session) {
if (DEBUG) {
- Slog.d(TAG, "Starting vibration " + session.getVibration().id + " on thread");
+ Slog.d(TAG, "Starting vibration " + session.getVibration().id + " on thread");
}
VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration());
session.setVibrationConductor(conductor);
- int mode = startAppOpModeLocked(session.getCallerInfo());
+ CallerInfo callerInfo = session.getCallerInfo();
+ int mode = startAppOpModeLocked(callerInfo);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -1093,19 +1098,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mCurrentSession = session;
if (!mCurrentSession.linkToDeath()) {
// Shouldn't happen. The method call already logs.
+ finishAppOpModeLocked(callerInfo);
mCurrentSession = null; // Aborted.
return Status.IGNORED_ERROR_TOKEN;
}
if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
// Shouldn't happen. The method call already logs.
session.setVibrationConductor(null); // Rejected by thread, clear it in session.
+ mCurrentSession.unlinkToDeath();
+ finishAppOpModeLocked(callerInfo);
mCurrentSession = null; // Aborted.
return Status.IGNORED_ERROR_SCHEDULING;
}
return null;
case AppOpsManager.MODE_ERRORED:
- Slog.w(TAG, "Start AppOpsManager operation errored for uid "
- + session.getCallerInfo().uid);
+ Slog.w(TAG, "Start AppOpsManager operation errored for uid " + callerInfo.uid);
return Status.IGNORED_ERROR_APP_OPS;
default:
return Status.IGNORED_APP_OPS;
@@ -1286,14 +1293,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- private void onVibrationComplete(int vibratorId, long vibrationId) {
+ private void onVibrationComplete(int vibratorId, long vibrationId, long stepId) {
synchronized (mLock) {
if (mCurrentSession != null) {
if (DEBUG) {
- Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
- + " complete, notifying thread");
+ Slog.d(TAG, "Vibration " + vibrationId + " step " + stepId
+ + " on vibrator " + vibratorId + " complete, notifying thread");
}
- mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId);
+ mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId, stepId);
}
}
}
@@ -1494,6 +1501,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private int checkAppOpModeLocked(CallerInfo callerInfo) {
int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
callerInfo.attrs.getAudioUsage(), callerInfo.uid, callerInfo.opPkg);
+ if (DEBUG) {
+ int opMode = mAppOps.checkOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid,
+ callerInfo.opPkg);
+ Slog.d(TAG, "Check AppOp mode VIBRATE for uid " + callerInfo.uid + " and package "
+ + callerInfo.opPkg + " returned audio=" + AppOpsManager.MODE_NAMES[mode]
+ + ", op=" + AppOpsManager.MODE_NAMES[opMode]);
+ }
int fixedMode = fixupAppOpModeLocked(mode, callerInfo.attrs);
if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) {
// If we're just ignoring the vibration op then this is set by DND and we should ignore
@@ -1507,9 +1521,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/** Start an operation in {@link AppOpsManager}, if allowed. */
@GuardedBy("mLock")
private int startAppOpModeLocked(CallerInfo callerInfo) {
- return fixupAppOpModeLocked(
- mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg),
- callerInfo.attrs);
+ int mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid,
+ callerInfo.opPkg);
+ if (DEBUG) {
+ Slog.d(TAG, "Start AppOp mode VIBRATE for uid " + callerInfo.uid + " and package "
+ + callerInfo.opPkg + " returned " + AppOpsManager.MODE_NAMES[mode]);
+ }
+ return fixupAppOpModeLocked(mode, callerInfo.attrs);
}
/**
@@ -1518,6 +1536,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*/
@GuardedBy("mLock")
private void finishAppOpModeLocked(CallerInfo callerInfo) {
+ if (DEBUG) {
+ Slog.d(TAG, "Finish AppOp mode VIBRATE for uid " + callerInfo.uid + " and package "
+ + callerInfo.opPkg);
+ }
mAppOps.finishOp(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg);
}
@@ -2078,10 +2100,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override
- public void onComplete(int vibratorId, long vibrationId) {
+ public void onComplete(int vibratorId, long vibrationId, long stepId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
- service.onVibrationComplete(vibratorId, vibrationId);
+ service.onVibrationComplete(vibratorId, vibrationId, stepId);
}
}
}
@@ -2651,7 +2673,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
CombinedVibration.ParallelCombination combination =
CombinedVibration.startParallel();
while ("-v".equals(getNextOption())) {
- int vibratorId = Integer.parseInt(getNextArgRequired());
+ int vibratorId = parseInt(getNextArgRequired(), "Expected vibrator id after -v");
combination.addVibrator(vibratorId, nextEffect());
}
runVibrate(commonOptions, combination.combine());
@@ -2663,7 +2685,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
CombinedVibration.SequentialCombination combination =
CombinedVibration.startSequential();
while ("-v".equals(getNextOption())) {
- int vibratorId = Integer.parseInt(getNextArgRequired());
+ int vibratorId = parseInt(getNextArgRequired(), "Expected vibrator id after -v");
combination.addNext(vibratorId, nextEffect());
}
runVibrate(commonOptions, combination.combine());
@@ -2688,7 +2710,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private int runHapticFeedback() {
CommonOptions commonOptions = new CommonOptions();
- int constant = Integer.parseInt(getNextArgRequired());
+ int constant = parseInt(getNextArgRequired(), "Expected haptic feedback constant id");
IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
: mShellCallbacksToken;
@@ -2736,12 +2758,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if ("-a".equals(nextOption)) {
hasAmplitude = true;
} else if ("-w".equals(nextOption)) {
- delay = Integer.parseInt(getNextArgRequired());
+ delay = parseInt(getNextArgRequired(), "Expected delay millis after -w");
}
}
- long duration = Long.parseLong(getNextArgRequired());
- int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired())
+ long duration = parseInt(getNextArgRequired(), "Expected one-shot duration millis");
+ int amplitude = hasAmplitude
+ ? parseInt(getNextArgRequired(), "Expected one-shot amplitude")
: VibrationEffect.DEFAULT_AMPLITUDE;
composition.addOffDuration(Duration.ofMillis(delay));
composition.addEffect(VibrationEffect.createOneShot(duration, amplitude));
@@ -2816,8 +2839,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
while ((nextOption = getNextOption()) != null) {
switch (nextOption) {
case "-a" -> isAdvanced = true;
- case "-i" -> initialSharpness = Float.parseFloat(getNextArgRequired());
- case "-r" -> repeat = Integer.parseInt(getNextArgRequired());
+ case "-i" -> initialSharpness = parseFloat(getNextArgRequired(),
+ "Expected initial sharpness after -i");
+ case "-r" -> repeat = parseInt(getNextArgRequired(),
+ "Expected repeat index after -r");
}
}
@@ -2843,8 +2868,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// nextArg is not a duration, finish reading.
break;
}
- intensity = Float.parseFloat(getNextArgRequired());
- sharpness = Float.parseFloat(getNextArgRequired());
+ intensity = parseFloat(getNextArgRequired(), "Expected envelope intensity");
+ sharpness = parseFloat(getNextArgRequired(), "Expected envelope sharpness");
builder.addControlPoint(intensity, sharpness, duration);
pos++;
}
@@ -2872,16 +2897,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
getNextArgRequired(); // consume "waveform"
String nextOption;
while ((nextOption = getNextOption()) != null) {
- if ("-a".equals(nextOption)) {
- hasAmplitudes = true;
- } else if ("-r".equals(nextOption)) {
- repeat = Integer.parseInt(getNextArgRequired());
- } else if ("-w".equals(nextOption)) {
- delay = Integer.parseInt(getNextArgRequired());
- } else if ("-f".equals(nextOption)) {
- hasFrequencies = true;
- } else if ("-c".equals(nextOption)) {
- isContinuous = true;
+ switch (nextOption) {
+ case "-a" -> hasAmplitudes = true;
+ case "-f" -> hasFrequencies = true;
+ case "-c" -> isContinuous = true;
+ case "-r" -> repeat = parseInt(getNextArgRequired(),
+ "Expected repeat index after -r");
+ case "-w" -> delay = parseInt(getNextArgRequired(),
+ "Expected delay millis after -w");
}
}
List<Integer> durations = new ArrayList<>();
@@ -2899,14 +2922,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
break;
}
if (hasAmplitudes) {
- amplitudes.add(
- Float.parseFloat(getNextArgRequired()) / VibrationEffect.MAX_AMPLITUDE);
+ int amplitude = parseInt(getNextArgRequired(), "Expected waveform amplitude");
+ amplitudes.add((float) amplitude / VibrationEffect.MAX_AMPLITUDE);
} else {
amplitudes.add(nextAmplitude);
nextAmplitude = 1 - nextAmplitude;
}
if (hasFrequencies) {
- frequencies.add(Float.parseFloat(getNextArgRequired()));
+ frequencies.add(
+ parseFloat(getNextArgRequired(), "Expected waveform frequency"));
}
}
@@ -2965,27 +2989,37 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if ("-b".equals(nextOption)) {
shouldFallback = true;
} else if ("-w".equals(nextOption)) {
- delay = Integer.parseInt(getNextArgRequired());
+ delay = parseInt(getNextArgRequired(), "Expected delay millis after -w");
}
}
- int effectId = Integer.parseInt(getNextArgRequired());
+ int effectId = parseInt(getNextArgRequired(), "Expected prebaked effect id");
composition.addOffDuration(Duration.ofMillis(delay));
composition.addEffect(VibrationEffect.get(effectId, shouldFallback));
}
private void addPrimitivesToComposition(VibrationEffect.Composition composition) {
getNextArgRequired(); // consume "primitives"
- String nextArg;
- while ((nextArg = peekNextArg()) != null) {
+ while (peekNextArg() != null) {
int delay = 0;
- if ("-w".equals(nextArg)) {
- getNextArgRequired(); // consume "-w"
- delay = Integer.parseInt(getNextArgRequired());
- nextArg = peekNextArg();
+ float scale = 1f;
+ int delayType = PrimitiveSegment.DEFAULT_DELAY_TYPE;
+
+ String nextOption;
+ while ((nextOption = getNextOption()) != null) {
+ if ("-s".equals(nextOption)) {
+ scale = parseFloat(getNextArgRequired(), "Expected scale after -s");
+ } else if ("-o".equals(nextOption)) {
+ delayType = VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
+ delay = parseInt(getNextArgRequired(), "Expected offset millis after -o");
+ } else if ("-w".equals(nextOption)) {
+ delayType = PrimitiveSegment.DEFAULT_DELAY_TYPE;
+ delay = parseInt(getNextArgRequired(), "Expected delay millis after -w");
+ }
}
try {
- composition.addPrimitive(Integer.parseInt(nextArg), /* scale= */ 1, delay);
+ String nextArg = peekNextArg(); // Just in case this is not a primitive.
+ composition.addPrimitive(Integer.parseInt(nextArg), scale, delay, delayType);
getNextArgRequired(); // consume the primitive id
} catch (NumberFormatException | NullPointerException e) {
// nextArg is not describing a primitive, leave it to be consumed by outer loops
@@ -3011,17 +3045,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
VibrationXmlParser.parseDocument(new StringReader(xml));
VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo();
if (combinedVibratorInfo == null) {
- throw new IllegalStateException(
- "No combined vibrator info to parse vibration XML " + xml);
+ throw new IllegalStateException("No vibrator info available to parse XML");
}
VibrationEffect effect = parsedVibration.resolve(combinedVibratorInfo);
if (effect == null) {
- throw new IllegalArgumentException(
- "Parsed vibration cannot be resolved for vibration XML " + xml);
+ throw new IllegalArgumentException("Parsed XML cannot be resolved: " + xml);
}
return CombinedVibration.createParallel(effect);
} catch (IOException e) {
- throw new RuntimeException("Error parsing vibration XML " + xml, e);
+ throw new RuntimeException("Error parsing XML: " + xml, e);
}
}
@@ -3039,16 +3071,30 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
+ private static int parseInt(String text, String errorMessage) {
+ try {
+ return Integer.parseInt(text);
+ } catch (NumberFormatException | NullPointerException e) {
+ throw new IllegalArgumentException(errorMessage, e);
+ }
+ }
+
+ private static float parseFloat(String text, String errorMessage) {
+ try {
+ return Float.parseFloat(text);
+ } catch (NumberFormatException | NullPointerException e) {
+ throw new IllegalArgumentException(errorMessage, e);
+ }
+ }
+
@Override
public void onHelp() {
try (PrintWriter pw = getOutPrintWriter();) {
pw.println("Vibrator Manager commands:");
pw.println(" help");
pw.println(" Prints this help text.");
- pw.println("");
pw.println(" list");
- pw.println(" Prints the id of device vibrators. This does not include any ");
- pw.println(" connected input device.");
+ pw.println(" Prints device vibrator ids; does not include input devices.");
pw.println(" synced [options] <effect>...");
pw.println(" Vibrates effect on all vibrators in sync.");
pw.println(" combined [options] (-v <vibrator-id> <effect>...)...");
@@ -3058,51 +3104,41 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
pw.println(" xml [options] <xml>");
pw.println(" Vibrates using combined vibration described in given XML string");
pw.println(" on all vibrators in sync. The XML could be:");
- pw.println(" XML containing a single effect, or");
- pw.println(" A vibration select XML containing multiple effects.");
- pw.println(" Vibrates using combined vibration described in given XML string.");
- pw.println(" XML containing a single effect it runs on all vibrators in sync.");
+ pw.println(" A single <vibration-effect>, or");
+ pw.println(" A <vibration-select> containing multiple effects.");
+ pw.println(" feedback [options] <constant>");
+ pw.println(" Performs a haptic feedback with the given constant.");
pw.println(" cancel");
pw.println(" Cancels any active vibration");
- pw.println(" feedback [-f] [-d <description>] <constant>");
- pw.println(" Performs a haptic feedback with the given constant.");
- pw.println(" The force (-f) option enables the `always` configuration, which");
- pw.println(" plays the haptic irrespective of the vibration intensity settings");
pw.println("");
pw.println("Effect commands:");
pw.println(" oneshot [-w delay] [-a] <duration> [<amplitude>]");
- pw.println(" Vibrates for duration milliseconds; ignored when device is on ");
- pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting ");
- pw.println(" will be used to scale amplitude.");
+ pw.println(" Vibrates for duration milliseconds.");
pw.println(" If -w is provided, the effect will be played after the specified");
pw.println(" wait time in milliseconds.");
pw.println(" If -a is provided, the command accepts a second argument for ");
pw.println(" amplitude, in a scale of 1-255.");
pw.print(" waveform [-w delay] [-r index] [-a] [-f] [-c] ");
pw.println("(<duration> [<amplitude>] [<frequency>])...");
- pw.println(" Vibrates for durations and amplitudes in list; ignored when ");
- pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength ");
- pw.println(" user setting will be used to scale amplitude.");
+ pw.println(" Vibrates for durations and amplitudes in list.");
pw.println(" If -w is provided, the effect will be played after the specified");
pw.println(" wait time in milliseconds.");
pw.println(" If -r is provided, the waveform loops back to the specified");
- pw.println(" index (e.g. 0 loops from the beginning)");
+ pw.println(" index (e.g. 0 loops from the beginning).");
pw.println(" If -a is provided, the command expects amplitude to follow each");
pw.println(" duration; otherwise, it accepts durations only and alternates");
- pw.println(" off/on");
+ pw.println(" off/on.");
pw.println(" If -f is provided, the command expects frequency to follow each");
- pw.println(" amplitude or duration; otherwise, it uses resonant frequency");
+ pw.println(" amplitude or duration; otherwise, it uses resonant frequency.");
pw.println(" If -c is provided, the waveform is continuous and will ramp");
pw.println(" between values; otherwise each entry is a fixed step.");
pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255;");
- pw.println(" frequency is an absolute value in hertz;");
+ pw.println(" frequency is an absolute value in hertz.");
pw.print(" envelope [-a] [-i initial sharpness] [-r index] ");
pw.println("[<duration1> <intensity1> <sharpness1>]...");
pw.println(" Generates a vibration pattern based on a series of duration, ");
pw.println(" intensity, and sharpness values. The total vibration time is ");
- pw.println(" the sum of all durations; Ignored when device is on ");
- pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting ");
- pw.println(" will be used to scale amplitude.");
+ pw.println(" the sum of all durations.");
pw.println(" If -a is provided, the waveform will use the advanced APIs to ");
pw.println(" generate the vibration pattern and the input parameters ");
pw.println(" become [<duration1> <amplitude1> <frequency1>].");
@@ -3111,19 +3147,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
pw.println(" If -r is provided, the waveform loops back to the specified index");
pw.println(" (e.g. 0 loops from the beginning).");
pw.println(" prebaked [-w delay] [-b] <effect-id>");
- pw.println(" Vibrates with prebaked effect; ignored when device is on DND ");
- pw.println(" (Do Not Disturb) mode; touch feedback strength user setting ");
- pw.println(" will be used to scale amplitude.");
+ pw.println(" Vibrates with prebaked effect.");
pw.println(" If -w is provided, the effect will be played after the specified");
pw.println(" wait time in milliseconds.");
pw.println(" If -b is provided, the prebaked fallback effect will be played if");
pw.println(" the device doesn't support the given effect-id.");
- pw.println(" primitives ([-w delay] <primitive-id>)...");
- pw.println(" Vibrates with a composed effect; ignored when device is on DND ");
- pw.println(" (Do Not Disturb) mode; touch feedback strength user setting ");
- pw.println(" will be used to scale primitive intensities.");
+ pw.print(" primitives ([-w delay] [-o time] [-s scale]");
+ pw.println("<primitive-id> [<scale>])...");
+ pw.println(" Vibrates with a composed effect.");
pw.println(" If -w is provided, the next primitive will be played after the ");
pw.println(" specified wait time in milliseconds.");
+ pw.println(" If -o is provided, the next primitive will be played at the ");
+ pw.println(" specified start offset time in milliseconds.");
+ pw.println(" If -s is provided, the next primitive will be played with the");
+ pw.println(" specified amplitude scale, in a scale of [0,1].");
pw.println("");
pw.println("Common Options:");
pw.println(" -f");
@@ -3134,6 +3171,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
pw.println(" -d <description>");
pw.println(" Add description to the vibration.");
pw.println("");
+ pw.println("Notes");
+ pw.println(" Vibrations triggered by these commands will be ignored when");
+ pw.println(" device is on DND (Do Not Disturb) mode; notification strength");
+ pw.println(" user settings will be applied for scale.");
+ pw.println("");
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ebadeac70dd1..7b6d408fbe2c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -109,7 +109,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
-import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.hasWindowExtensionsEnabled;
@@ -321,9 +320,7 @@ import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
-import android.view.AppTransitionAnimationSpec;
import android.view.DisplayInfo;
-import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.InputApplicationHandle;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -493,6 +490,7 @@ final class ActivityRecord extends WindowToken {
private long createTime = System.currentTimeMillis();
long lastVisibleTime; // last time this activity became visible
long pauseTime; // last time we started pausing the activity
+ long mStoppedTime; // last time we completely stopped the activity
long launchTickTime; // base time for launch tick messages
long topResumedStateLossTime; // last time we reported top resumed state loss to an activity
// Last configuration reported to the activity in the client process.
@@ -2348,7 +2346,8 @@ final class ActivityRecord extends WindowToken {
// The snapshot of home is only used once because it won't be updated while screen
// is on (see {@link TaskSnapshotController#screenTurningOff}).
mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
- if ((mDisplayContent.mAppTransition.getTransitFlags()
+ final Transition transition = mTransitionController.getCollectingTransition();
+ if (transition != null && (transition.getFlags()
& WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
// Only use snapshot of home as starting window when unlocking directly.
return false;
@@ -3637,7 +3636,6 @@ final class ActivityRecord extends WindowToken {
if (DEBUG_VISIBILITY || DEBUG_TRANSITION) {
Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this);
}
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
// When finishing the activity preemptively take the snapshot before the app window
// is marked as hidden and any configuration changes take place
@@ -3739,7 +3737,6 @@ final class ActivityRecord extends WindowToken {
private void prepareActivityHideTransitionAnimation() {
final DisplayContent dc = mDisplayContent;
- dc.prepareAppTransition(TRANSIT_CLOSE);
setVisibility(false);
dc.executeAppTransition();
}
@@ -4391,13 +4388,6 @@ final class ActivityRecord extends WindowToken {
removeStartingWindow();
}
- // If app transition animation was running for this activity, then we need to ensure that
- // the app transition notifies that animations have completed in
- // DisplayContent.handleAnimatingStoppedAndTransition(), so add to that list now
- if (isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
- getDisplayContent().mNoAnimationNotifyOnTransitionFinished.add(token);
- }
-
if (delayed && !isEmpty()) {
// set the token aside because it has an active animation to be finished
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
@@ -5069,8 +5059,6 @@ final class ActivityRecord extends WindowToken {
void applyOptionsAnimation() {
if (DEBUG_TRANSITION) Slog.i(TAG, "Applying options for " + this);
if (mPendingRemoteAnimation != null) {
- mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
- mPendingRemoteAnimation);
mTransitionController.setStatusBarTransitionDelay(
mPendingRemoteAnimation.getStatusBarTransitionDelay());
} else {
@@ -5100,14 +5088,6 @@ final class ActivityRecord extends WindowToken {
IRemoteCallback finishCallback = null;
switch (animationType) {
case ANIM_CUSTOM:
- displayContent.mAppTransition.overridePendingAppTransition(
- pendingOptions.getPackageName(),
- pendingOptions.getCustomEnterResId(),
- pendingOptions.getCustomExitResId(),
- pendingOptions.getCustomBackgroundColor(),
- pendingOptions.getAnimationStartedListener(),
- pendingOptions.getAnimationFinishedListener(),
- pendingOptions.getOverrideTaskTransition());
options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
pendingOptions.getCustomBackgroundColor(),
@@ -5116,9 +5096,6 @@ final class ActivityRecord extends WindowToken {
finishCallback = pendingOptions.getAnimationFinishedListener();
break;
case ANIM_CLIP_REVEAL:
- displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getWidth(), pendingOptions.getHeight());
options = AnimationOptions.makeClipRevealAnimOptions(
pendingOptions.getStartX(), pendingOptions.getStartY(),
pendingOptions.getWidth(), pendingOptions.getHeight());
@@ -5130,9 +5107,6 @@ final class ActivityRecord extends WindowToken {
}
break;
case ANIM_SCALE_UP:
- displayContent.mAppTransition.overridePendingAppTransitionScaleUp(
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getWidth(), pendingOptions.getHeight());
options = AnimationOptions.makeScaleUpAnimOptions(
pendingOptions.getStartX(), pendingOptions.getStartY(),
pendingOptions.getWidth(), pendingOptions.getHeight(),
@@ -5148,10 +5122,6 @@ final class ActivityRecord extends WindowToken {
case ANIM_THUMBNAIL_SCALE_DOWN:
final boolean scaleUp = (animationType == ANIM_THUMBNAIL_SCALE_UP);
final HardwareBuffer buffer = pendingOptions.getThumbnail();
- displayContent.mAppTransition.overridePendingAppTransitionThumb(buffer,
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getAnimationStartedListener(),
- scaleUp);
options = AnimationOptions.makeThumbnailAnimOptions(buffer,
pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp);
startCallback = pendingOptions.getAnimationStartedListener();
@@ -5164,36 +5134,9 @@ final class ActivityRecord extends WindowToken {
break;
case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
- final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs();
- final IAppTransitionAnimationSpecsFuture specsFuture =
- pendingOptions.getSpecsFuture();
- if (specsFuture != null) {
- displayContent.mAppTransition.overridePendingAppTransitionMultiThumbFuture(
- specsFuture, pendingOptions.getAnimationStartedListener(),
- animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP);
- } else if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
- && specs != null) {
- displayContent.mAppTransition.overridePendingAppTransitionMultiThumb(
- specs, pendingOptions.getAnimationStartedListener(),
- pendingOptions.getAnimationFinishedListener(), false);
- } else {
- displayContent.mAppTransition.overridePendingAppTransitionAspectScaledThumb(
- pendingOptions.getThumbnail(),
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getWidth(), pendingOptions.getHeight(),
- pendingOptions.getAnimationStartedListener(),
- (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP));
- if (intent.getSourceBounds() == null) {
- intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
- pendingOptions.getStartY(),
- pendingOptions.getStartX() + pendingOptions.getWidth(),
- pendingOptions.getStartY() + pendingOptions.getHeight()));
- }
- }
+ // TODO(b/397847511): remove the related types from ActivityOptions.
break;
case ANIM_OPEN_CROSS_PROFILE_APPS:
- displayContent.mAppTransition
- .overridePendingAppTransitionStartCrossProfileApps();
options = AnimationOptions.makeCrossProfileAnimOptions();
break;
case ANIM_NONE:
@@ -5450,8 +5393,6 @@ final class ActivityRecord extends WindowToken {
}
private void setVisibility(boolean visible, boolean deferHidingClient) {
- final AppTransition appTransition = getDisplayContent().mAppTransition;
-
// Don't set visibility to false if we were already not visible. This prevents WM from
// adding the app to the closing app list which doesn't make sense for something that is
// already not visible. However, set visibility to true even if we are already visible.
@@ -5471,8 +5412,8 @@ final class ActivityRecord extends WindowToken {
}
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s",
- token, visible, appTransition, isVisible(), mVisibleRequested,
+ "setAppVisibility(%s, visible=%b): visible=%b mVisibleRequested=%b Callers=%s",
+ token, visible, isVisible(), mVisibleRequested,
Debug.getCallers(6));
// Before setting mVisibleRequested so we can track changes.
@@ -5569,15 +5510,6 @@ final class ActivityRecord extends WindowToken {
updateReportedVisibilityLocked();
}
- @Override
- boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
- boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
- if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- return false;
- }
- return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources);
- }
-
/**
* Update visibility to this {@link ActivityRecord}.
*
@@ -6447,6 +6379,7 @@ final class ActivityRecord extends WindowToken {
Slog.w(TAG, "Exception thrown during pause", e);
// Just in case, assume it to be stopped.
mAppStopped = true;
+ mStoppedTime = SystemClock.uptimeMillis();
ProtoLog.v(WM_DEBUG_STATES, "Stop failed; moving to STOPPED: %s", this);
setState(STOPPED, "stopIfPossible");
}
@@ -6480,6 +6413,7 @@ final class ActivityRecord extends WindowToken {
if (isStopping) {
ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPED: %s (stop complete)", this);
+ mStoppedTime = SystemClock.uptimeMillis();
setState(STOPPED, "activityStopped");
}
@@ -6639,9 +6573,7 @@ final class ActivityRecord extends WindowToken {
// starting window is drawn, the transition can start earlier. Exclude finishing and bubble
// because it may be a trampoline.
if (app == null && !finishing && !mLaunchedFromBubble
- && mVisibleRequested && !mDisplayContent.mAppTransition.isReady()
- && !mDisplayContent.mAppTransition.isRunning()
- && mDisplayContent.isNextTransitionForward()) {
+ && mVisibleRequested && mDisplayContent.isNextTransitionForward()) {
// The pending transition state will be cleared after the transition is started, so
// save the state for launching the client later (used by LaunchActivityItem).
mStartingData.mIsTransitionForward = true;
@@ -7523,7 +7455,6 @@ final class ActivityRecord extends WindowToken {
}
}
- getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);
scheduleAnimation();
// Schedule to handle the stopping and finishing activities which the animation is done
@@ -9644,7 +9575,18 @@ final class ActivityRecord extends WindowToken {
&& !mDisplayContent.isSleeping()) {
// Visibility of starting activities isn't calculated until pause-complete, so if
// this is not paused yet, don't consider it ready.
- return false;
+ // However, due to pip1 having an intermediate state, add a special exception here
+ // that skips waiting if the next activity is already visible.
+ final ActivityRecord toResume = isPip2ExperimentEnabled() ? null
+ : mDisplayContent.getActivity((r) -> !r.finishing
+ && r.isVisibleRequested()
+ && !r.isTaskOverlay()
+ && !r.isAlwaysOnTop());
+ if (toResume == null || !toResume.isVisible()) {
+ return false;
+ } else {
+ Slog.i(TAG, "Assuming sync-finish while pausing due to visible target");
+ }
}
return true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 0a57cb50d681..c243cdc23137 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -617,6 +617,9 @@ public abstract class ActivityTaskManagerInternal {
*/
public abstract boolean isBaseOfLockedTask(String packageName);
+ /** Returns the value of {@link android.R.attr#windowNoDisplay} from the given theme. */
+ public abstract boolean isNoDisplay(String packageName, int theme, int userId);
+
/**
* Creates an interface to update configuration for the calling application.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4e2fade599ba..6f83822ee97a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2130,6 +2130,26 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
+ public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) {
+ enforceTaskPermission("setTaskIsPerceptible()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final Task task = mRootWindowContainer.anyTaskForId(taskId,
+ MATCH_ATTACHED_TASK_ONLY);
+ if (task == null) {
+ Slog.w(TAG, "setTaskIsPerceptible: No task to set with id=" + taskId);
+ return false;
+ }
+ task.mIsPerceptible = isPerceptible;
+ }
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public boolean removeTask(int taskId) {
mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()");
synchronized (mGlobalLock) {
@@ -7448,6 +7468,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
+ public boolean isNoDisplay(String packageName, int theme, int userId) {
+ if (!com.android.window.flags.Flags.cacheWindowStyle()) {
+ final AttributeCache.Entry ent = AttributeCache.instance()
+ .get(packageName, theme, R.styleable.Window, userId);
+ return ent != null
+ && ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+ }
+ final ActivityRecord.WindowStyle style = getWindowStyle(packageName, theme, userId);
+ return style != null && style.noDisplay();
+ }
+
+ @Override
public PackageConfigurationUpdater createPackageConfigurationUpdater() {
return new PackageConfigurationUpdaterImpl(Binder.getCallingPid(),
ActivityTaskManagerService.this);
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b932ef362aca..6718ae435cd9 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -748,14 +748,9 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
&& policy.okToAnimate(true /* ignoreScreenOn */)) {
return false;
}
- // Consider unoccluding only when all unknown visibilities have been
- // resolved, as otherwise we just may be starting another occluding activity.
- final boolean isUnoccluding =
- mDisplayContent.mAppTransition.isUnoccluding()
- && mDisplayContent.mUnknownAppVisibilityController.allResolved();
- // If keyguard is showing, or we're unoccluding, force the keyguard's orientation,
+ // Use keyguard's orientation if it is showing and not occluded
// even if SystemUI hasn't updated the attrs yet.
- if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
+ if (policy.isKeyguardShowingAndNotOccluded()) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fd322a5c6345..9353cede49b2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -88,7 +88,6 @@ import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
import static android.window.DisplayAreaOrganizer.FEATURE_IME;
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTENT_RECORDING;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
@@ -162,6 +161,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -368,12 +368,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private MetricsLogger mMetricsLogger;
- /**
- * List of clients without a transtiton animation that we notify once we are done
- * transitioning since they won't be notified through the app window animator.
- */
- final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>();
-
// Mapping from a token IBinder to a WindowToken object on this display.
private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
@@ -456,6 +450,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private DisplayInfo mLastDisplayInfoOverride;
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ @NonNull
private final DisplayPolicy mDisplayPolicy;
private final DisplayRotation mDisplayRotation;
@@ -542,6 +537,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Remove this display when animation on it has completed. */
private boolean mDeferredRemoval;
+ @NonNull
final PinnedTaskController mPinnedTaskController;
private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();
@@ -826,11 +822,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
final ActivityRecord focusedApp = mFocusedApp;
+ final boolean canReceiveKeys = w.canReceiveKeys();
ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
- w, w.mAttrs.flags, w.canReceiveKeys(),
+ w, w.mAttrs.flags, canReceiveKeys,
w.canReceiveKeysReason(false /* fromUserTouch */));
- if (!w.canReceiveKeys()) {
+ if (!canReceiveKeys) {
return false;
}
@@ -1102,6 +1099,29 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
};
/**
+ * Called to update fields retrieve from {@link #getDisplayUiContext()} resources when
+ * there's a configuration update on {@link #getDisplayUiContext()}.
+ */
+ @NonNull
+ private final ComponentCallbacks mSysUiContextConfigCallback = new ComponentCallbacks() {
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ synchronized (mWmService.mGlobalLock) {
+ if (mDisplayReady) {
+ mDisplayPolicy.onConfigurationChanged();
+ mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
+ }
+ }
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+ };
+
+ /**
* Create new {@link DisplayContent} instance, add itself to the root window container and
* initialize direct children.
* @param display May not be null.
@@ -1145,8 +1165,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mFixedRotationTransitionListener = new FixedRotationTransitionListener(mDisplayId);
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
- mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
- mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
@@ -2797,11 +2815,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final int lastOrientation = getConfiguration().orientation;
final int lastWindowingMode = getWindowingMode();
super.onConfigurationChanged(newParentConfig);
- if (mDisplayPolicy != null) {
- mDisplayPolicy.onConfigurationChanged();
- mPinnedTaskController.onPostDisplayConfigurationChanged();
- mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
+ if (!Flags.trackSystemUiContextBeforeWms()) {
+ mSysUiContextConfigCallback.onConfigurationChanged(newParentConfig);
}
+ mPinnedTaskController.onPostDisplayConfigurationChanged();
// Update IME parent if needed.
updateImeParent();
@@ -2833,13 +2850,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return isVisible() && !mRemoved && !mRemoving;
}
- @Override
- void onAppTransitionDone() {
- super.onAppTransitionDone();
- mWmService.mWindowsChanged = true;
- onTransitionFinished();
- }
-
void onTransitionFinished() {
// If the transition finished callback cannot match the token for some reason, make sure the
// rotated state is cleared if it is already invisible.
@@ -3360,9 +3370,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDeferredRemoval = false;
try {
mUnknownAppVisibilityController.clear();
- mAppTransition.removeAppTransitionTimeoutCallbacks();
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
- handleAnimatingStoppedAndTransition();
mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
@@ -3381,6 +3389,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
.getKeyguardController().onDisplayRemoved(mDisplayId);
mWallpaperController.resetLargestDisplay(mDisplay);
mWmService.mDisplayWindowSettings.onDisplayRemoved(this);
+ if (Flags.trackSystemUiContextBeforeWms()) {
+ getDisplayUiContext().unregisterComponentCallbacks(mSysUiContextConfigCallback);
+ }
} finally {
mDisplayReady = false;
}
@@ -3542,11 +3553,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION);
mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
- if (mTransitionController.isShellTransitionsEnabled()) {
- mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
- } else {
- mAppTransition.dumpDebug(proto, APP_TRANSITION);
- }
+ mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
if (mFocusedApp != null) {
mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
}
@@ -3817,7 +3824,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (mTmpWindow == null) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d",
getDisplayId());
- return null;
}
return mTmpWindow;
}
@@ -5429,7 +5435,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
reconfigureDisplayLocked();
onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
- // Attach the SystemUiContext to this DisplayContent the get latest configuration.
+ // Attach the SystemUiContext to this DisplayContent to get latest configuration.
// Note that the SystemUiContext will be removed automatically if this DisplayContent
// is detached.
registerSystemUiContext();
@@ -5437,11 +5443,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
private void registerSystemUiContext() {
+ final Context systemUiContext = getDisplayUiContext();
final WindowProcessController wpc = mAtmService.getProcessController(
- getDisplayUiContext().getIApplicationThread());
+ systemUiContext.getIApplicationThread());
mWmService.mWindowContextListenerController.registerWindowContainerListener(
- wpc, getDisplayUiContext().getWindowContextToken(), this,
+ wpc, systemUiContext.getWindowContextToken(), this,
INVALID_WINDOW_TYPE, null /* options */);
+ if (Flags.trackSystemUiContextBeforeWms()) {
+ systemUiContext.registerComponentCallbacks(mSysUiContextConfigCallback);
+ }
}
@Override
@@ -5603,61 +5613,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* @see AppTransition#prepareAppTransition
*/
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
- @WindowManager.TransitionFlags int flags) {
- prepareAppTransition(transit, flags);
- mTransitionController.requestTransitionIfNeeded(transit, flags, null /* trigger */, this);
+ @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
+ mTransitionController.requestTransitionIfNeeded(transit, flags, trigger, this);
}
void executeAppTransition() {
mTransitionController.setReady(this);
- if (mAppTransition.isTransitionSet()) {
- ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
- "Execute app transition: %s, displayId: %d Callers=%s",
- mAppTransition, mDisplayId, Debug.getCallers(5));
- mAppTransition.setReady();
- mWmService.mWindowPlacerLocked.requestTraversal();
- }
- }
-
- /**
- * Update pendingLayoutChanges after app transition has finished.
- */
- void handleAnimatingStoppedAndTransition() {
- int changes = 0;
-
- mAppTransition.setIdle();
-
- for (int i = mNoAnimationNotifyOnTransitionFinished.size() - 1; i >= 0; i--) {
- final IBinder token = mNoAnimationNotifyOnTransitionFinished.get(i);
- mAppTransition.notifyAppTransitionFinishedLocked(token);
- }
- mNoAnimationNotifyOnTransitionFinished.clear();
-
- mWallpaperController.hideDeferredWallpapersIfNeededLegacy();
-
- onAppTransitionDone();
-
- changes |= FINISH_LAYOUT_REDO_LAYOUT;
- ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout");
- computeImeTarget(true /* updateImeTarget */);
- mWallpaperMayChange = true;
- // Since the window list has been rebuilt, focus might have to be recomputed since the
- // actual order of windows might have changed again.
- mWmService.mFocusMayChange = true;
-
- pendingLayoutChanges |= changes;
}
/** Check if pending app transition is for activity / task launch. */
boolean isNextTransitionForward() {
// TODO(b/191375840): decouple "forwardness" from transition system.
- if (mTransitionController.isShellTransitionsEnabled()) {
- @WindowManager.TransitionType int type =
- mTransitionController.getCollectingTransitionType();
- return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
- }
- return mAppTransition.containsTransitRequest(TRANSIT_OPEN)
- || mAppTransition.containsTransitRequest(TRANSIT_TO_FRONT);
+ final @WindowManager.TransitionType int type =
+ mTransitionController.getCollectingTransitionType();
+ return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
}
/**
@@ -6620,6 +6589,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); });
}
+ @NonNull
Context getDisplayUiContext() {
return mDisplayPolicy.getSystemUiContext();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 10f591cfd379..7aa2101f516c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -631,7 +631,6 @@ public class DisplayPolicy {
mHandler.post(mAppTransitionFinished);
}
};
- displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener);
// TODO: Make it can take screenshot on external display
@@ -1865,6 +1864,7 @@ public class DisplayPolicy {
return mContext;
}
+ @NonNull
Context getSystemUiContext() {
return mUiContext;
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6091b8334438..6d73739e5046 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -250,7 +250,7 @@ class KeyguardController {
// to the locked state before holding the sleep token again
if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
dc.requestTransitionAndLegacyPrepare(
- TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
+ TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
}
dc.mWallpaperController.adjustWallpaperWindows();
dc.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java
index 69463433827f..b3cff9c6cc3d 100644
--- a/services/core/java/com/android/server/wm/PresentationController.java
+++ b/services/core/java/com/android/server/wm/PresentationController.java
@@ -56,11 +56,6 @@ class PresentationController {
ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s",
win.getDisplayId(), win);
mPresentingDisplayIds.add(win.getDisplayId());
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
- /*notifyClients=*/ true);
- }
win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true);
}
@@ -76,11 +71,6 @@ class PresentationController {
if (displayIdIndex != -1) {
mPresentingDisplayIds.remove(displayIdIndex);
}
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
- /*notifyClients=*/ true);
- }
win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 40f16c187f20..f309372ab6a2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2103,10 +2103,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
}
- // Set a transition to ensure that we don't immediately try and update the visibility
- // of the activity entering PIP
- r.getDisplayContent().prepareAppTransition(TRANSIT_NONE);
-
transitionController.collect(task);
// Defer the windowing mode change until after the transition to prevent the activity
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6b3499a5d68c..6cd1336122be 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -54,7 +54,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -504,6 +503,17 @@ class Task extends TaskFragment {
int mOffsetYForInsets;
/**
+ * When set to true, the task will be kept at a PERCEPTIBLE_APP_ADJ, and downgraded
+ * to PREVIOUS_APP_ADJ if not in foreground for a period of time.
+ * One example use case is for desktop form factors, where it is important keep tasks in the
+ * perceptible state (rather than cached where it may be frozen) when a user moves it to the
+ * foreground.
+ * On startup, restored Tasks will not be perceptible, until user actually interacts with it
+ * (i.e. brings it to the foreground)
+ */
+ boolean mIsPerceptible = false;
+
+ /**
* Whether the compatibility overrides that change the resizability of the app should be allowed
* for the specific app.
*/
@@ -1647,8 +1657,7 @@ class Task extends TaskFragment {
// Prevent the transition from being executed too early if the top activity is
// resumed but the mVisibleRequested of any other activity is true, the transition
// should wait until next activity resumed.
- if (r.isState(RESUMED) || (r.isVisible()
- && !mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CLOSE))) {
+ if (r.isState(RESUMED) || r.isVisible()) {
r.finishIfPossible(reason, false /* oomAdj */);
} else {
r.destroyIfPossible(reason);
@@ -2324,11 +2333,6 @@ class Task extends TaskFragment {
mLastSurfaceSize.set(width, height);
}
- @VisibleForTesting
- boolean isInChangeTransition() {
- return AppTransition.isChangeTransitOld(mTransit);
- }
-
@Override
void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
@@ -3854,6 +3858,7 @@ class Task extends TaskFragment {
pw.print(ActivityInfo.resizeModeToString(mResizeMode));
pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture);
pw.print(" isResizeable="); pw.println(isResizeable());
+ pw.print(" isPerceptible="); pw.println(mIsPerceptible);
pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents);
@@ -5293,11 +5298,9 @@ class Task extends TaskFragment {
// Place a new activity at top of root task, so it is next to interact with the user.
if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
- mDisplayContent.prepareAppTransition(TRANSIT_NONE);
mTaskSupervisor.mNoAnimActivities.add(r);
mTransitionController.setNoAnimation(r);
} else {
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
mTaskSupervisor.mNoAnimActivities.remove(r);
}
if (newTask && !r.mLaunchTaskBehind) {
@@ -5477,7 +5480,8 @@ class Task extends TaskFragment {
Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
Task finishedTask = r.getTask();
- mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
+ mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED,
+ finishedTask);
r.finishIfPossible(reason, false /* oomAdj */);
// Also terminate any activities below it that aren't yet stopped, to avoid a situation
@@ -5695,7 +5699,6 @@ class Task extends TaskFragment {
ActivityOptions.abort(options);
}
}
- mDisplayContent.prepareAppTransition(transit);
}
final void moveTaskToFront(Task tr, boolean noAnimation, ActivityOptions options,
@@ -5747,12 +5750,9 @@ class Task extends TaskFragment {
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
if (noAnimation) {
- mDisplayContent.prepareAppTransition(TRANSIT_NONE);
mTaskSupervisor.mNoAnimActivities.add(top);
- if (mTransitionController.isShellTransitionsEnabled()) {
- mTransitionController.collect(top);
- mTransitionController.setNoAnimation(top);
- }
+ mTransitionController.collect(top);
+ mTransitionController.setNoAnimation(top);
ActivityOptions.abort(options);
} else {
updateTransitLocked(TRANSIT_TO_FRONT, options);
@@ -5862,10 +5862,6 @@ class Task extends TaskFragment {
moveTaskToBackInner(tr, transition);
});
} else {
- // Skip the transition for pinned task.
- if (!inPinnedWindowingMode()) {
- mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK);
- }
moveTaskToBackInner(tr, null /* transition */);
}
return true;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a698a9e82929..9d18d6c4cc89 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -44,10 +44,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -56,7 +52,6 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
@@ -1682,36 +1677,15 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final DisplayContent dc = taskDisplayArea.mDisplayContent;
if (prev != null) {
if (prev.finishing) {
- if (DEBUG_TRANSITION) {
- Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev);
- }
if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- dc.prepareAppTransition(TRANSIT_CLOSE);
}
prev.setVisibility(false);
- } else {
- if (DEBUG_TRANSITION) {
- Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev);
- }
- if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
- anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- dc.prepareAppTransition(TRANSIT_OPEN,
- next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
- }
- }
- } else {
- if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
- if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+ } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- dc.prepareAppTransition(TRANSIT_OPEN);
}
+ } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+ anim = false;
}
if (anim) {
@@ -1910,7 +1884,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (!hasDirectChildActivities()) {
return false;
}
- if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) {
+ if (mResumedActivity != null && !mResumedActivity.finishing
+ && mTransitionController.isTransientLaunch(mResumedActivity)) {
// Even if the transient activity is occluded, defer pausing (addToStopping will still
// be called) it until the transient transition is done. So the current resuming
// activity won't need to wait for additional pause complete.
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 242883612124..e612d8ec0ce6 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -20,6 +20,7 @@ import static android.graphics.Matrix.MSCALE_X;
import static android.graphics.Matrix.MSCALE_Y;
import static android.graphics.Matrix.MSKEW_X;
import static android.graphics.Matrix.MSKEW_Y;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TPL;
@@ -35,6 +36,7 @@ import android.util.ArrayMap;
import android.util.IntArray;
import android.util.Pair;
import android.util.Size;
+import android.util.SparseArray;
import android.view.InputWindowHandle;
import android.window.ITrustedPresentationListener;
import android.window.TrustedPresentationThresholds;
@@ -251,7 +253,7 @@ public class TrustedPresentationListenerController {
Rect tmpLogicalDisplaySize = new Rect();
Matrix tmpInverseMatrix = new Matrix();
float[] tmpMatrix = new float[9];
- Region coveredRegionsAbove = new Region();
+ SparseArray<Region> coveredRegionsAboveByDisplay = new SparseArray<>();
long currTimeMs = System.currentTimeMillis();
ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length);
@@ -262,7 +264,7 @@ public class TrustedPresentationListenerController {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
continue;
}
- var displayFound = false;
+ int displayId = INVALID_DISPLAY;
tmpRectF.set(windowHandle.frame);
for (var displayHandle : mLastWindowHandles.second) {
if (displayHandle.mDisplayId == windowHandle.displayId) {
@@ -273,17 +275,18 @@ public class TrustedPresentationListenerController {
tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(),
displayHandle.mLogicalSize.getHeight());
tmpRect.intersect(tmpLogicalDisplaySize);
- displayFound = true;
+ displayId = displayHandle.mDisplayId;
break;
}
}
- if (!displayFound) {
+ if (displayId == INVALID_DISPLAY) {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name,
windowHandle.displayId);
continue;
}
+ Region coveredRegionsAbove = coveredRegionsAboveByDisplay.get(displayId, new Region());
var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
if (listeners != null) {
Region region = new Region();
@@ -304,6 +307,7 @@ public class TrustedPresentationListenerController {
}
coveredRegionsAbove.op(tmpRect, Region.Op.UNION);
+ coveredRegionsAboveByDisplay.put(displayId, coveredRegionsAbove);
ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s",
windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove);
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 7c88abcec7ec..9506ffeb2792 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -217,8 +217,7 @@ class WallpaperWindowToken extends WindowToken {
}
// If in a transition, defer commits for activities that are going invisible
- if (!visible && (mTransitionController.inTransition()
- || getDisplayContent().mAppTransition.isRunning())) {
+ if (!visible && mTransitionController.inTransition()) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 45202a29ba97..d0d2067ac4bc 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -16,9 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -29,24 +26,15 @@ import static android.content.pm.ActivityInfo.reverseOrientation;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowInsets.Type.InsetsType;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SYNC_ENGINE;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
-import static com.android.server.wm.AppTransition.isActivityTransitOld;
-import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld;
-import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -65,7 +53,6 @@ import static com.android.server.wm.WindowContainerProto.VISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
import android.annotation.ColorInt;
@@ -81,10 +68,8 @@ import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.Pair;
import android.util.Pools;
import android.util.RotationUtils;
import android.util.Slog;
@@ -102,14 +87,11 @@ import android.view.SurfaceControl.Builder;
import android.view.SurfaceControlViewHost;
import android.view.WindowManager;
import android.view.WindowManager.TransitionOldType;
-import android.view.animation.Animation;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.common.LogLevel;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -1315,31 +1297,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
- * Returns true if the container or one of its children as some content it can display or wants
- * to display (e.g. app views or saved surface).
- *
- * NOTE: While this method will return true if the there is some content to display, it doesn't
- * mean the container is visible. Use {@link #isVisible()} to determine if the container is
- * visible.
- */
- boolean hasContentToDisplay() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mChildren.get(i);
- if (wc.hasContentToDisplay()) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Returns true if the container or one of its children is considered visible from the
* WindowManager perspective which usually means valid surface and some other internal state
* are true.
*
* NOTE: While this method will return true if the surface is visible, it doesn't mean the
- * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if
- * the container has any content to display.
+ * client has actually displayed any content.
*/
boolean isVisible() {
// TODO: Will this be more correct if it checks the visibility of its parents?
@@ -1480,13 +1443,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
- void onAppTransitionDone() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mChildren.get(i);
- wc.onAppTransitionDone();
- }
- }
-
/**
* Called when this container or one of its descendants changed its requested orientation, and
* wants this container to handle it or pass it to its parent.
@@ -3039,264 +2995,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
getRelativePosition(outPosition);
}
- /**
- * Applies the app transition animation according the given the layout properties in the
- * window hierarchy.
- *
- * @param lp The layout parameters of the window.
- * @param transit The app transition type indicates what kind of transition to be applied.
- * @param enter Whether the app transition is entering transition or not.
- * @param isVoiceInteraction Whether the container is participating in voice interaction or not.
- * @param sources {@link ActivityRecord}s which causes this app transition animation.
- *
- * @return {@code true} when the container applied the app transition, {@code false} if the
- * app transition is disabled or skipped.
- *
- * @see #getAnimationAdapter
- */
- boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,
- boolean enter, boolean isVoiceInteraction,
- @Nullable ArrayList<WindowContainer> sources) {
- if (mWmService.mDisableTransitionAnimation) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: transition animation is disabled or skipped. "
- + "container=%s", this);
- cancelAnimation();
- return false;
- }
-
- // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
- // to animate and it can cause strange artifacts when we unfreeze the display if some
- // different animation is running.
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
- if (okToAnimate()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: transit=%s, enter=%b, wc=%s",
- AppTransition.appTransitionOldToString(transit), enter, this);
- applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
- } else {
- cancelAnimation();
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- return isAnimating();
- }
-
- /**
- * Gets the {@link AnimationAdapter} according the given window layout properties in the window
- * hierarchy.
- *
- * @return The return value will always contain two elements, one for normal animations and the
- * other for thumbnail animation, both can be {@code null}.
- *
- * @See com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord
- * @See LocalAnimationAdapter
- */
- Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
- @TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) {
- final Pair<AnimationAdapter, AnimationAdapter> resultAdapters;
- final int appRootTaskClipMode = getDisplayContent().mAppTransition.getAppRootTaskClipMode();
-
- // Separate position and size for use in animators.
- final Rect screenBounds = getAnimationBounds(appRootTaskClipMode);
- mTmpRect.set(screenBounds);
- getAnimationPosition(mTmpPoint);
- mTmpRect.offsetTo(0, 0);
-
- final boolean isChanging = AppTransition.isChangeTransitOld(transit);
-
- if (isChanging) {
- final float durationScale = mWmService.getTransitionAnimationScaleLocked();
- final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
- mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
-
- final AnimationAdapter adapter = new LocalAnimationAdapter(
- new WindowChangeAnimationSpec(null /* startBounds */, mTmpRect,
- displayInfo, durationScale, true /* isAppAnimation */,
- false /* isThumbnail */),
- getSurfaceAnimationRunner());
-
- final AnimationAdapter thumbnailAdapter = null;
- resultAdapters = new Pair<>(adapter, thumbnailAdapter);
- mTransit = transit;
- mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
- } else {
- mNeedsAnimationBoundsLayer = (appRootTaskClipMode == ROOT_TASK_CLIP_AFTER_ANIM);
- final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
-
- if (a != null) {
- // Only apply corner radius to animation if we're not in multi window mode.
- // We don't want rounded corners when in pip or split screen.
- final float windowCornerRadius = !inMultiWindowMode()
- ? getDisplayContent().getWindowCornerRadius()
- : 0;
- if (asActivityRecord() != null
- && asActivityRecord().isNeedsLetterboxedAnimation()) {
- asActivityRecord().getLetterboxInnerBounds(mTmpRect);
- }
- AnimationAdapter adapter = new LocalAnimationAdapter(
- new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
- getDisplayContent().mAppTransition.canSkipFirstFrame(),
- appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
- getSurfaceAnimationRunner());
-
- resultAdapters = new Pair<>(adapter, null);
- mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP
- || AppTransition.isClosingTransitOld(transit);
- mTransit = transit;
- mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
- } else {
- resultAdapters = new Pair<>(null, null);
- }
- }
- return resultAdapters;
- }
-
- protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
- @TransitionOldType int transit, boolean isVoiceInteraction,
- @Nullable ArrayList<WindowContainer> sources) {
- final Task task = asTask();
- if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) {
- final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
- final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null
- && imeTarget.getWindow().getTask() == task;
- // Attach and show the IME screenshot when the task is the IME target and performing
- // task closing transition to the next task.
- if (isImeLayeringTarget && AppTransition.isTaskCloseTransitOld(transit)) {
- mDisplayContent.showImeScreenshot();
- }
- }
- final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
- transit, enter, isVoiceInteraction);
- AnimationAdapter adapter = adapters.first;
- AnimationAdapter thumbnailAdapter = adapters.second;
- if (adapter != null) {
- if (sources != null) {
- mSurfaceAnimationSources.addAll(sources);
- }
-
- AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
-
- // Check if the animation requests to show background color for Activity and embedded
- // TaskFragment.
- final ActivityRecord activityRecord = asActivityRecord();
- final TaskFragment taskFragment = asTaskFragment();
- if (adapter.getShowBackground()
- // Check if it is Activity transition.
- && ((activityRecord != null && isActivityTransitOld(transit))
- // Check if it is embedded TaskFragment transition.
- || (taskFragment != null && taskFragment.isEmbedded()
- && isTaskFragmentTransitOld(transit)))) {
- final @ColorInt int backgroundColorForTransition;
- if (adapter.getBackgroundColor() != 0) {
- // If available use the background color provided through getBackgroundColor
- // which if set originates from a call to overridePendingAppTransition.
- backgroundColorForTransition = adapter.getBackgroundColor();
- } else {
- final TaskFragment organizedTf = activityRecord != null
- ? activityRecord.getOrganizedTaskFragment()
- : taskFragment.getOrganizedTaskFragment();
- if (organizedTf != null && organizedTf.getAnimationParams()
- .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
- // This window is embedded and has an animation background color set on the
- // TaskFragment. Pass this color with this window, so the handler can use it
- // as the animation background color if needed,
- backgroundColorForTransition = organizedTf.getAnimationParams()
- .getAnimationBackgroundColor();
- } else {
- // Otherwise default to the window's background color if provided through
- // the theme as the background color for the animation - the top most window
- // with a valid background color and showBackground set takes precedence.
- final Task parentTask = activityRecord != null
- ? activityRecord.getTask()
- : taskFragment.getTask();
- backgroundColorForTransition = parentTask.getTaskDescription()
- .getBackgroundColor();
- }
- }
- // Set to opaque for animation background to prevent it from exposing the blank
- // background or content below.
- animationRunnerBuilder.setTaskBackgroundColor(ColorUtils.setAlphaComponent(
- backgroundColorForTransition, 255));
- }
-
- animationRunnerBuilder.build()
- .startAnimation(getPendingTransaction(), adapter, !isVisible(),
- ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
-
- if (adapter.getShowWallpaper()) {
- getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- }
- }
- }
-
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
return mWmService.mSurfaceAnimationRunner;
}
- private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
- boolean isVoiceInteraction) {
- if ((isOrganized()
- // TODO(b/161711458): Clean-up when moved to shell.
- && getWindowingMode() != WINDOWING_MODE_FULLSCREEN
- && getWindowingMode() != WINDOWING_MODE_FREEFORM
- && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) {
- return null;
- }
-
- final DisplayContent displayContent = getDisplayContent();
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final int width = displayInfo.appWidth;
- final int height = displayInfo.appHeight;
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: container=%s", this);
-
- // Determine the visible rect to calculate the thumbnail clip with
- // getAnimationFrames.
- final Rect frame = new Rect(0, 0, width, height);
- final Rect displayFrame = new Rect(0, 0,
- displayInfo.logicalWidth, displayInfo.logicalHeight);
- final Rect insets = new Rect();
- final Rect stableInsets = new Rect();
- final Rect surfaceInsets = new Rect();
- getAnimationFrames(frame, insets, stableInsets, surfaceInsets);
-
- if (mLaunchTaskBehind) {
- // Differentiate the two animations. This one which is briefly on the screen
- // gets the !enter animation, and the other one which remains on the
- // screen gets the enter animation. Both appear in the mOpeningApps set.
- enter = false;
- }
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
- "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s "
- + "surfaceInsets=%s",
- AppTransition.appTransitionOldToString(transit), enter, frame, insets,
- surfaceInsets);
- final Configuration displayConfig = displayContent.getConfiguration();
- final Animation a = getDisplayContent().mAppTransition.loadAnimation(lp, transit, enter,
- displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
- surfaceInsets, stableInsets, isVoiceInteraction, inFreeformWindowingMode(), this);
- if (a != null) {
- if (a != null) {
- // Setup the maximum app transition duration to prevent malicious app may set a long
- // animation duration or infinite repeat counts for the app transition through
- // ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition.
- a.restrictDuration(MAX_APP_TRANSITION_DURATION);
- }
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
- ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s",
- a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20));
- }
- final int containingWidth = frame.width();
- final int containingHeight = frame.height();
- a.initialize(containingWidth, containingHeight, width, height);
- a.scaleCurrentDuration(mWmService.getTransitionAnimationScaleLocked());
- }
- return a;
- }
-
boolean canCreateRemoteAnimationTarget() {
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d699a689459e..8aed91b2dc66 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -157,6 +157,7 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -1820,125 +1821,158 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean hideSystemAlertWindows = shouldHideNonSystemOverlayWindow(win);
win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
- boolean imMayMove = true;
-
- win.mToken.addWindow(win);
- displayPolicy.addWindowLw(win, attrs);
- displayPolicy.setDropInputModePolicy(win, win.mAttrs);
- if (type == TYPE_APPLICATION_STARTING && activity != null) {
- activity.attachStartingWindow(win);
- ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
- activity, win);
- } else if (type == TYPE_INPUT_METHOD
- // IME window is always touchable.
- // Ignore non-touchable windows e.g. Stylus InkWindow.java.
- && (win.getAttrs().flags & FLAG_NOT_TOUCHABLE) == 0) {
- displayContent.setInputMethodWindowLocked(win);
- imMayMove = false;
- } else if (type == TYPE_INPUT_METHOD_DIALOG) {
- displayContent.computeImeTarget(true /* updateImeTarget */);
- imMayMove = false;
- } else {
- if (type == TYPE_WALLPAPER) {
- displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
- displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- } else if (win.hasWallpaper()) {
- displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
- // If there is currently a wallpaper being shown, and
- // the base layer of the new window is below the current
- // layer of the target window, then adjust the wallpaper.
- // This is to avoid a new window being placed between the
- // wallpaper and its target.
- displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ // Only a presentation window needs a transition because its visibility affets the
+ // lifecycle of apps below (b/390481865).
+ if (enablePresentationForConnectedDisplays() && win.isPresentation()) {
+ Transition transition = null;
+ if (!win.mTransitionController.isCollecting()) {
+ transition = win.mTransitionController.createAndStartCollecting(TRANSIT_OPEN);
+ }
+ win.mTransitionController.collect(win.mToken);
+ res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
+ outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
+ // A presentation hides all activities behind on the same display.
+ win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ win.mTransitionController.getCollectingTransition().setReady(win.mToken, true);
+ if (transition != null) {
+ win.mTransitionController.requestStartTransition(transition, null,
+ null /* remoteTransition */, null /* displayChange */);
}
+ } else {
+ res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
+ outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
}
+ }
- final WindowStateAnimator winAnimator = win.mWinAnimator;
- winAnimator.mEnterAnimationPending = true;
- winAnimator.mEnteringAnimation = true;
+ Binder.restoreCallingIdentity(origId);
- if (displayPolicy.areSystemBarsForcedConsumedLw()) {
- res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
- }
- if (displayContent.isInTouchMode()) {
- res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
- }
- if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) {
- res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
+ return res;
+ }
+
+ private int addWindowInner(@NonNull WindowState win, @NonNull DisplayPolicy displayPolicy,
+ @NonNull ActivityRecord activity, @NonNull DisplayContent displayContent,
+ @NonNull InsetsState outInsetsState, @NonNull Rect outAttachedFrame,
+ @NonNull InsetsSourceControl.Array outActiveControls, @NonNull IWindow client,
+ @NonNull float[] outSizeCompatScale, @NonNull LayoutParams attrs) {
+ int res = 0;
+ final int type = attrs.type;
+ boolean imMayMove = true;
+
+ win.mToken.addWindow(win);
+ displayPolicy.addWindowLw(win, attrs);
+ displayPolicy.setDropInputModePolicy(win, win.mAttrs);
+ if (type == TYPE_APPLICATION_STARTING && activity != null) {
+ activity.attachStartingWindow(win);
+ ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
+ activity, win);
+ } else if (type == TYPE_INPUT_METHOD
+ // IME window is always touchable.
+ // Ignore non-touchable windows e.g. Stylus InkWindow.java.
+ && (win.getAttrs().flags & FLAG_NOT_TOUCHABLE) == 0) {
+ displayContent.setInputMethodWindowLocked(win);
+ imMayMove = false;
+ } else if (type == TYPE_INPUT_METHOD_DIALOG) {
+ displayContent.computeImeTarget(true /* updateImeTarget */);
+ imMayMove = false;
+ } else {
+ if (type == TYPE_WALLPAPER) {
+ displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
+ displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ } else if (win.hasWallpaper()) {
+ displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
+ // If there is currently a wallpaper being shown, and
+ // the base layer of the new window is below the current
+ // layer of the target window, then adjust the wallpaper.
+ // This is to avoid a new window being placed between the
+ // wallpaper and its target.
+ displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
+ }
- displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
+ final WindowStateAnimator winAnimator = win.mWinAnimator;
+ winAnimator.mEnterAnimationPending = true;
+ winAnimator.mEnteringAnimation = true;
- boolean focusChanged = false;
- if (win.canReceiveKeys()) {
- focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
- false /*updateInputWindows*/);
- if (focusChanged) {
- imMayMove = false;
- }
- }
+ if (displayPolicy.areSystemBarsForcedConsumedLw()) {
+ res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
+ }
+ if (displayContent.isInTouchMode()) {
+ res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
+ }
+ if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) {
+ res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
+ }
- if (imMayMove) {
- displayContent.computeImeTarget(true /* updateImeTarget */);
- if (win.isImeOverlayLayeringTarget()) {
- dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,
- win.isVisibleRequestedOrAdding(), false /* removed */,
- displayContent.getDisplayId());
- }
- }
+ displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
- // Don't do layout here, the window must call
- // relayout to be displayed, so we'll do it there.
- if (win.mActivityRecord != null && win.mActivityRecord.isEmbedded()) {
- // Assign child layers from the parent Task if the Activity is embedded.
- win.getTask().assignChildLayers();
- } else {
- win.getParent().assignChildLayers();
+ boolean focusChanged = false;
+ if (win.canReceiveKeys()) {
+ focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
+ false /*updateInputWindows*/);
+ if (focusChanged) {
+ imMayMove = false;
}
+ }
- if (focusChanged) {
- displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
- false /*updateInputWindows*/);
+ if (imMayMove) {
+ displayContent.computeImeTarget(true /* updateImeTarget */);
+ if (win.isImeOverlayLayeringTarget()) {
+ dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,
+ win.isVisibleRequestedOrAdding(), false /* removed */,
+ displayContent.getDisplayId());
}
- displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
+ }
- ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
- + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
+ // Don't do layout here, the window must call
+ // relayout to be displayed, so we'll do it there.
+ if (win.mActivityRecord != null && win.mActivityRecord.isEmbedded()) {
+ // Assign child layers from the parent Task if the Activity is embedded.
+ win.getTask().assignChildLayers();
+ } else {
+ win.getParent().assignChildLayers();
+ }
- boolean needToSendNewConfiguration =
- win.isVisibleRequestedOrAdding() && displayContent.updateOrientation();
- if (win.providesDisplayDecorInsets()) {
- needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo();
- }
- if (needToSendNewConfiguration) {
- displayContent.sendNewConfiguration();
- }
+ if (focusChanged) {
+ displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
+ false /*updateInputWindows*/);
+ }
+ displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
- // This window doesn't have a frame yet. Don't let this window cause the insets change.
- displayContent.getInsetsStateController().updateAboveInsetsState(
- false /* notifyInsetsChanged */);
+ ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
+ + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
- win.fillInsetsState(outInsetsState, true /* copySources */);
- getInsetsSourceControls(win, outActiveControls);
+ boolean needToSendNewConfiguration =
+ win.isVisibleRequestedOrAdding() && displayContent.updateOrientation();
+ if (win.providesDisplayDecorInsets()) {
+ needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo();
+ }
+ if (needToSendNewConfiguration) {
+ displayContent.sendNewConfiguration();
+ }
- if (win.mLayoutAttached) {
- outAttachedFrame.set(win.getParentWindow().getFrame());
- if (win.mInvGlobalScale != 1f) {
- outAttachedFrame.scale(win.mInvGlobalScale);
- }
- } else {
- // Make this invalid which indicates a null attached frame.
- outAttachedFrame.set(0, 0, -1, -1);
- }
- outSizeCompatScale[0] = win.getCompatScaleForClient();
+ // This window doesn't have a frame yet. Don't let this window cause the insets change.
+ displayContent.getInsetsStateController().updateAboveInsetsState(
+ false /* notifyInsetsChanged */);
+
+ win.fillInsetsState(outInsetsState, true /* copySources */);
+ getInsetsSourceControls(win, outActiveControls);
- if (res >= ADD_OKAY && win.isPresentation()) {
- mPresentationController.onPresentationAdded(win);
+ if (win.mLayoutAttached) {
+ outAttachedFrame.set(win.getParentWindow().getFrame());
+ if (win.mInvGlobalScale != 1f) {
+ outAttachedFrame.scale(win.mInvGlobalScale);
}
+ } else {
+ // Make this invalid which indicates a null attached frame.
+ outAttachedFrame.set(0, 0, -1, -1);
}
+ outSizeCompatScale[0] = win.getCompatScaleForClient();
- Binder.restoreCallingIdentity(origId);
+ if (res >= ADD_OKAY && win.isPresentation()) {
+ mPresentationController.onPresentationAdded(win);
+ }
return res;
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 30dde543b9d4..270de0197a4e 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -37,6 +37,7 @@ import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STARTED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -344,6 +345,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
*/
private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
+ /**
+ * The most recent timestamp of when one of this process's stopped activities in a
+ * perceptible task became stopped. Written by window manager and read by activity manager.
+ */
+ private volatile long mPerceptibleTaskStoppedTimeMillis = Long.MIN_VALUE;
+
public WindowProcessController(@NonNull ActivityTaskManagerService atm,
@NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
@NonNull WindowProcessListener listener) {
@@ -475,8 +482,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
r.detachFromProcess();
if (r.isVisibleRequested()) {
hasVisibleActivity = true;
+ Task finishingTask = r.getTask();
r.mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE,
- TRANSIT_FLAG_APP_CRASHED);
+ TRANSIT_FLAG_APP_CRASHED, finishingTask);
}
r.destroyIfPossible("handleAppCrashed");
}
@@ -1228,6 +1236,17 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return mActivityStateFlags;
}
+ /**
+ * Returns the most recent timestamp when one of this process's stopped activities in a
+ * perceptible task became stopped. It should only be called if {@link #hasActivities}
+ * returns {@code true} and {@link #getActivityStateFlags} does not have any of
+ * the ACTIVITY_STATE_FLAG_IS_(VISIBLE|PAUSING_OR_PAUSED|STOPPING) bit set.
+ */
+ @HotPath(caller = HotPath.OOM_ADJUSTMENT)
+ public long getPerceptibleTaskStoppedTimeMillis() {
+ return mPerceptibleTaskStoppedTimeMillis;
+ }
+
void computeProcessActivityState() {
// Since there could be more than one activities in a process record, we don't need to
// compute the OomAdj with each of them, just need to find out the activity with the
@@ -1239,6 +1258,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
int minTaskLayer = Integer.MAX_VALUE;
int stateFlags = 0;
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;
@@ -1287,6 +1307,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
bestInvisibleState = STOPPING;
// Not "finishing" if any of activity isn't finishing.
allStoppingFinishing &= r.finishing;
+ } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) {
+ if (task.mIsPerceptible) {
+ perceptibleTaskStoppedTimeMillis =
+ Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis);
+ }
}
}
}
@@ -1324,6 +1349,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;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9f1289b2c12a..bfedc90497ae 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -95,6 +95,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
@@ -182,6 +183,7 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
import android.annotation.CallSuper;
@@ -1696,17 +1698,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|| mActivityRecord.isStartingWindowDisplayed());
}
- @Override
- boolean hasContentToDisplay() {
- if (!isDrawn() && (mViewVisibility == View.VISIBLE
- || (isAnimating(TRANSITION | PARENTS)
- && !getDisplayContent().mAppTransition.isTransitionSet()))) {
- return true;
- }
-
- return super.hasContentToDisplay();
- }
-
private boolean isVisibleByPolicyOrInsets() {
return isVisibleByPolicy()
// If we don't have a provider, this window isn't used as a window generating
@@ -2297,11 +2288,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
dc.updateImeInputAndControlTarget(null);
}
- final int type = mAttrs.type;
-
- if (isPresentation()) {
- mWmService.mPresentationController.onPresentationRemoved(this);
- }
// Check if window provides non decor insets before clearing its provided insets.
final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets();
@@ -2442,11 +2428,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- removeImmediately();
- mWmService.updateFocusedWindowLocked(isFocused()
- ? UPDATE_FOCUS_REMOVING_FOCUS
- : UPDATE_FOCUS_NORMAL,
- true /*updateInputWindows*/);
+ // Only a presentation window needs a transition because its visibility affets the
+ // lifecycle of apps below (b/390481865).
+ if (enablePresentationForConnectedDisplays() && isPresentation()) {
+ Transition transition = null;
+ if (!mTransitionController.isCollecting()) {
+ transition = mTransitionController.createAndStartCollecting(TRANSIT_CLOSE);
+ }
+ mTransitionController.collect(mToken);
+ mAnimatingExit = true;
+ mRemoveOnExit = true;
+ mToken.setVisibleRequested(false);
+ mWmService.mPresentationController.onPresentationRemoved(this);
+ // A presentation hides all activities behind on the same display.
+ mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ mTransitionController.getCollectingTransition().setReady(mToken, true);
+ if (transition != null) {
+ mTransitionController.requestStartTransition(transition, null,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ } else {
+ removeImmediately();
+ mWmService.updateFocusedWindowLocked(isFocused()
+ ? UPDATE_FOCUS_REMOVING_FOCUS
+ : UPDATE_FOCUS_NORMAL,
+ true /*updateInputWindows*/);
+ }
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index d26539c377a9..95776088aad8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -204,6 +204,7 @@ cc_defaults {
"android.frameworks.sensorservice-V1-ndk",
"android.frameworks.stats@1.0",
"android.frameworks.stats-V2-ndk",
+ "android.os.vibrator.flags-aconfig-cc",
"android.system.suspend.control-V1-cpp",
"android.system.suspend.control.internal-cpp",
"android.system.suspend-V1-ndk",
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index abd4cd25cf68..534dbb1f6cf1 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -21,6 +21,7 @@
#include <android/binder_parcel_jni.h>
#include <android/hardware/vibrator/1.3/IVibrator.h>
#include <android/persistable_bundle_aidl.h>
+#include <android_os_vibrator.h>
#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -143,21 +144,23 @@ public:
return mHal->doWithRetry(fn, functionName);
}
- std::function<void()> createCallback(jlong vibrationId) {
+ std::function<void()> createCallback(jlong vibrationId, jlong stepId) {
auto callbackId = ++mCallbackId;
- return [vibrationId, callbackId, this]() {
+ return [vibrationId, stepId, callbackId, this]() {
auto currentCallbackId = mCallbackId.load();
- if (currentCallbackId != callbackId) {
- // This callback is from an older HAL call that is no longer relevant to the service
+ if (!android_os_vibrator_fix_vibration_thread_callback_handling() &&
+ currentCallbackId != callbackId) {
+ // This callback is from an older HAL call that is no longer relevant
return;
}
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
- jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId,
- vibrationId);
+ jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId, vibrationId,
+ stepId);
};
}
void disableOldCallbacks() {
+ // TODO remove this once android_os_vibrator_fix_vibration_thread_callback_handling removed
mCallbackId++;
}
@@ -165,6 +168,7 @@ private:
const std::shared_ptr<vibrator::HalController> mHal;
const int32_t mVibratorId;
const jobject mCallbackListener;
+ // TODO remove this once android_os_vibrator_fix_vibration_thread_callback_handling removed
std::atomic<int64_t> mCallbackId;
};
@@ -273,13 +277,13 @@ static jboolean vibratorIsAvailable(JNIEnv* env, jclass /* clazz */, jlong ptr)
}
static jlong vibratorOn(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong timeoutMs,
- jlong vibrationId) {
+ jlong vibrationId, jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorOn failed because native wrapper was not initialized");
return -1;
}
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto onFn = [timeoutMs, &callback](vibrator::HalWrapper* hal) {
return hal->on(std::chrono::milliseconds(timeoutMs), callback);
};
@@ -324,7 +328,7 @@ static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong pt
}
static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong effect,
- jlong strength, jlong vibrationId) {
+ jlong strength, jlong vibrationId, jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformEffect failed because native wrapper was not initialized");
@@ -332,7 +336,7 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j
}
Aidl::Effect effectType = static_cast<Aidl::Effect>(effect);
Aidl::EffectStrength effectStrength = static_cast<Aidl::EffectStrength>(strength);
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto performEffectFn = [effectType, effectStrength, &callback](vibrator::HalWrapper* hal) {
return hal->performEffect(effectType, effectStrength, callback);
};
@@ -342,7 +346,7 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j
static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
jobject vendorData, jlong strength, jfloat scale,
- jfloat adaptiveScale, jlong vibrationId) {
+ jfloat adaptiveScale, jlong vibrationId, jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized");
@@ -350,7 +354,7 @@ static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong
}
Aidl::VendorEffect effect =
vendorEffectFromJavaParcel(env, vendorData, strength, scale, adaptiveScale);
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) {
return hal->performVendorEffect(effect, callback);
};
@@ -359,7 +363,8 @@ static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong
}
static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobjectArray composition, jlong vibrationId) {
+ jobjectArray composition, jlong vibrationId,
+ jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformComposedEffect failed because native wrapper was not initialized");
@@ -371,7 +376,7 @@ static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlon
jobject element = env->GetObjectArrayElement(composition, i);
effects.push_back(effectFromJavaPrimitive(env, element));
}
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto performComposedEffectFn = [&effects, &callback](vibrator::HalWrapper* hal) {
return hal->performComposedEffect(effects, callback);
};
@@ -381,7 +386,8 @@ static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlon
}
static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobjectArray waveform, jint brakingId, jlong vibrationId) {
+ jobjectArray waveform, jint brakingId, jlong vibrationId,
+ jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformPwleEffect failed because native wrapper was not initialized");
@@ -406,7 +412,7 @@ static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong pt
}
}
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto performPwleEffectFn = [&primitives, &callback](vibrator::HalWrapper* hal) {
return hal->performPwleEffect(primitives, callback);
};
@@ -415,7 +421,7 @@ static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong pt
}
static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobjectArray waveform, jlong vibrationId) {
+ jobjectArray waveform, jlong vibrationId, jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformPwleV2Effect failed because native wrapper was not initialized");
@@ -431,7 +437,7 @@ static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong
}
composite.pwlePrimitives = primitives;
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto composePwleV2Fn = [&composite, &callback](vibrator::HalWrapper* hal) {
return hal->composePwleV2(composite, callback);
};
@@ -610,16 +616,16 @@ static const JNINativeMethod method_table[] = {
(void*)vibratorNativeInit},
{"getNativeFinalizer", "()J", (void*)vibratorGetNativeFinalizer},
{"isAvailable", "(J)Z", (void*)vibratorIsAvailable},
- {"on", "(JJJ)J", (void*)vibratorOn},
+ {"on", "(JJJJ)J", (void*)vibratorOn},
{"off", "(J)V", (void*)vibratorOff},
{"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
- {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
- {"performVendorEffect", "(JLandroid/os/Parcel;JFFJ)J", (void*)vibratorPerformVendorEffect},
- {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
+ {"performEffect", "(JJJJJ)J", (void*)vibratorPerformEffect},
+ {"performVendorEffect", "(JLandroid/os/Parcel;JFFJJ)J", (void*)vibratorPerformVendorEffect},
+ {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;JJ)J",
(void*)vibratorPerformComposedEffect},
- {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
+ {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJJ)J",
(void*)vibratorPerformPwleEffect},
- {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;J)J",
+ {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;JJ)J",
(void*)vibratorPerformPwleV2Effect},
{"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
{"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
@@ -632,7 +638,7 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
auto listenerClassName =
"com/android/server/vibrator/VibratorController$OnVibrationCompleteListener";
jclass listenerClass = FindClassOrDie(env, listenerClassName);
- sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V");
+ sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJJ)V");
jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment");
sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 191c21e661d0..aee32a0473a3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -423,6 +423,7 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.hardware.usb.UsbManager;
+import android.health.connect.HealthConnectManager;
import android.location.Location;
import android.location.LocationManager;
import android.media.AudioManager;
@@ -2149,6 +2150,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
mBackgroundHandler = BackgroundThread.getHandler();
+ // Add the health permission to the list of restricted permissions.
+ if (android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) {
+ Set<String> healthPermissions = HealthConnectManager.getHealthPermissions(mContext);
+ for (String permission : healthPermissions) {
+ SENSOR_PERMISSIONS.add(permission);
+ }
+ }
+
// Needed when mHasFeature == false, because it controls the certificate warning text.
mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 2d3f7231cc5c..856466675a28 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -40,10 +40,14 @@ import android.content.res.Configuration;
import android.graphics.Insets;
import android.os.Build;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.server.wm.WindowManagerStateHelper;
import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
@@ -72,6 +76,7 @@ import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -139,10 +144,9 @@ public class InputMethodServiceTest {
if (!mOriginalVerboseImeTrackerLoggingEnabled) {
setVerboseImeTrackerLogging(true);
}
+ mUiDevice.setOrientationNatural();
prepareIme();
prepareActivity();
- mUiDevice.freezeRotation();
- mUiDevice.setOrientationNatural();
// Waits for input binding ready.
eventually(() -> {
mInputMethodService = InputMethodServiceWrapper.getInstance();
@@ -169,6 +173,9 @@ public class InputMethodServiceTest {
@After
public void tearDown() throws Exception {
+ if (!mUiDevice.isNaturalOrientation()) {
+ mUiDevice.setOrientationNatural();
+ }
mUiDevice.unfreezeRotation();
if (!mOriginalVerboseImeTrackerLoggingEnabled) {
setVerboseImeTrackerLogging(false);
@@ -245,6 +252,61 @@ public class InputMethodServiceTest {
}
/**
+ * This checks that the surface is removed after the window was hidden in
+ * InputMethodService#hideSoftInput
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testSurfaceRemovedAfterHideSoftInput() {
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ // Triggers to show IME via public API.
+ verifyInputViewStatusOnMainSync(() -> mActivity.showImeWithWindowInsetsController(),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var window = mInputMethodService.getWindow().getWindow();
+ assertWithMessage("IME window exists").that(window).isNotNull();
+ assertWithMessage("IME window showing").that(
+ window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE);
+
+ mActivity.getWindow().getDecorView().setWindowInsetsAnimationCallback(
+ new WindowInsetsAnimation.Callback(
+ WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
+ @NonNull
+ @Override
+ public WindowInsetsAnimation.Bounds onStart(
+ @NonNull WindowInsetsAnimation animation,
+ @NonNull WindowInsetsAnimation.Bounds bounds) {
+ return super.onStart(animation, bounds);
+ }
+
+ @NonNull
+ @Override
+ public WindowInsets onProgress(@NonNull WindowInsets insets,
+ @NonNull List<WindowInsetsAnimation> runningAnimations) {
+ assertWithMessage("IME surface not removed during the animation").that(
+ window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE);
+ return insets;
+ }
+
+ @Override
+ public void onEnd(@NonNull WindowInsetsAnimation animation) {
+ assertWithMessage(
+ "IME surface not removed before the end of the animation").that(
+ window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE);
+ super.onEnd(animation);
+ }
+ });
+
+ // Triggers to hide IME via public API.
+ verifyInputViewStatusOnMainSync(() -> mActivity.hideImeWithWindowInsetsController(),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
+ eventually(() -> assertWithMessage("IME window not showing").that(
+ window.getDecorView().getVisibility()).isEqualTo(View.GONE));
+ }
+
+ /**
* This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
*/
@Test
@@ -866,6 +928,9 @@ public class InputMethodServiceTest {
() -> mActivity.showImeWithWindowInsetsController(),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ eventually(() -> assertWithMessage("IME navigation bar is shown")
+ .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue());
+
final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
verifyInputViewStatus(
() -> {
@@ -892,6 +957,9 @@ public class InputMethodServiceTest {
() -> mActivity.showImeWithWindowInsetsController(),
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ eventually(() -> assertWithMessage("IME navigation bar is shown")
+ .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue());
+
final var backButton = getUiObject(By.res(INPUT_METHOD_NAV_BACK_ID));
verifyInputViewStatus(
() -> {
@@ -927,6 +995,9 @@ public class InputMethodServiceTest {
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ eventually(() -> assertWithMessage("IME navigation bar is shown")
+ .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue());
+
final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
imeSwitcherButton.click();
mInstrumentation.waitForIdleSync();
@@ -965,6 +1036,9 @@ public class InputMethodServiceTest {
},
EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ eventually(() -> assertWithMessage("IME navigation bar is shown")
+ .that(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue());
+
final var imeSwitcherButton = getUiObject(By.res(INPUT_METHOD_NAV_IME_SWITCHER_ID));
imeSwitcherButton.longClick();
mInstrumentation.waitForIdleSync();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 7d25acd7f5e7..a42116351c2d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -152,7 +152,7 @@ public class AutomaticBrightnessControllerTest {
}
@Override
- AutomaticBrightnessController.Clock createClock(boolean isEnabled) {
+ AutomaticBrightnessController.Clock createClock() {
return new AutomaticBrightnessController.Clock() {
@Override
public long uptimeMillis() {
@@ -618,39 +618,46 @@ public class AutomaticBrightnessControllerTest {
long increment = 500;
// set autobrightness to low
// t = 0
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
// t = 500
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
// t = 1000
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
// t = 1500
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
// t = 2000
// ensure that our reading is at 0.
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
// t = 2500
// first 10000 lux sensor event reading
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertTrue(mController.getAmbientLux() > 0.0f);
assertTrue(mController.getAmbientLux() < 10000.0f);
// t = 3000
// lux reading should still not yet be 10000.
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertTrue(mController.getAmbientLux() > 0.0f);
assertTrue(mController.getAmbientLux() < 10000.0f);
@@ -659,45 +666,53 @@ public class AutomaticBrightnessControllerTest {
// lux has been high (10000) for 1000ms.
// lux reading should be 10000
// short horizon (ambient lux) is high, long horizon is still not high
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
// t = 4000
// stay high
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
// t = 4500
Mockito.clearInvocations(mBrightnessMappingStrategy);
mClock.fastForward(increment);
// short horizon is high, long horizon is high too
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1);
assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
// t = 5000
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertTrue(mController.getAmbientLux() > 0.0f);
assertTrue(mController.getAmbientLux() < 10000.0f);
// t = 5500
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertTrue(mController.getAmbientLux() > 0.0f);
assertTrue(mController.getAmbientLux() < 10000.0f);
// t = 6000
mClock.fastForward(increment);
// ambient lux goes to 0
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
// only the values within the horizon should be kept
assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
EPSILON);
- assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
+ assertArrayEquals(new long[]{4000 + ANDROID_SLEEP_TIME, 4500 + ANDROID_SLEEP_TIME,
+ 5000 + ANDROID_SLEEP_TIME, 5500 + ANDROID_SLEEP_TIME,
+ 6000 + ANDROID_SLEEP_TIME},
mController.getLastSensorTimestamps());
}
@@ -793,7 +808,8 @@ public class AutomaticBrightnessControllerTest {
for (int i = 0; i < 1000; i++) {
lux += increment;
mClock.fastForward(increment);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
}
int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
@@ -807,17 +823,17 @@ public class AutomaticBrightnessControllerTest {
long sensorTimestamp = mClock.now();
for (int i = valuesCount - 1; i >= 1; i--) {
assertEquals(lux, sensorValues[i], EPSILON);
- assertEquals(sensorTimestamp, sensorTimestamps[i]);
+ assertEquals(sensorTimestamp + ANDROID_SLEEP_TIME, sensorTimestamps[i]);
lux -= increment;
sensorTimestamp -= increment;
}
assertEquals(lux, sensorValues[0], EPSILON);
- assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG + ANDROID_SLEEP_TIME,
+ sensorTimestamps[0]);
}
@Test
public void testAmbientLuxBuffers_prunedBeyondLongHorizonExceptLatestValue() throws Exception {
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
@@ -867,7 +883,8 @@ public class AutomaticBrightnessControllerTest {
for (int i = 0; i < 20; i++) {
lux += increment1;
mClock.fastForward(increment1);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
}
int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
@@ -877,7 +894,8 @@ public class AutomaticBrightnessControllerTest {
for (int i = 0; i < initialCapacity - valuesCount; i++) {
lux += increment2;
mClock.fastForward(increment2);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
}
float[] sensorValues = mController.getLastSensorValues();
@@ -890,7 +908,7 @@ public class AutomaticBrightnessControllerTest {
long sensorTimestamp = mClock.now();
for (int i = initialCapacity - 1; i >= 1; i--) {
assertEquals(lux, sensorValues[i], EPSILON);
- assertEquals(sensorTimestamp, sensorTimestamps[i]);
+ assertEquals(sensorTimestamp + ANDROID_SLEEP_TIME, sensorTimestamps[i]);
if (i >= valuesCount) {
lux -= increment2;
@@ -901,7 +919,8 @@ public class AutomaticBrightnessControllerTest {
}
}
assertEquals(lux, sensorValues[0], EPSILON);
- assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG + ANDROID_SLEEP_TIME,
+ sensorTimestamps[0]);
}
@Test
@@ -951,25 +970,29 @@ public class AutomaticBrightnessControllerTest {
// t = 0
// Initial lux
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(500, mController.getAmbientLux(), EPSILON);
// t = 1000
// Lux isn't steady yet
mClock.fastForward(1000);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(500, mController.getAmbientLux(), EPSILON);
// t = 1500
// Lux isn't steady yet
mClock.fastForward(500);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(500, mController.getAmbientLux(), EPSILON);
// t = 2500
// Lux is steady now
mClock.fastForward(1000);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(1200, mController.getAmbientLux(), EPSILON);
}
@@ -992,25 +1015,29 @@ public class AutomaticBrightnessControllerTest {
// t = 0
// Initial lux
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(1200, mController.getAmbientLux(), EPSILON);
// t = 2000
// Lux isn't steady yet
mClock.fastForward(2000);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(1200, mController.getAmbientLux(), EPSILON);
// t = 2500
// Lux isn't steady yet
mClock.fastForward(500);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(1200, mController.getAmbientLux(), EPSILON);
// t = 4500
// Lux is steady now
mClock.fastForward(2000);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(500, mController.getAmbientLux(), EPSILON);
}
@@ -1031,19 +1058,22 @@ public class AutomaticBrightnessControllerTest {
// t = 0
// Initial lux
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(500, mController.getAmbientLux(), EPSILON);
// t = 500
// Lux isn't steady yet
mClock.fastForward(500);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(500, mController.getAmbientLux(), EPSILON);
// t = 1500
// Lux is steady now
mClock.fastForward(1000);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(1200, mController.getAmbientLux(), EPSILON);
}
@@ -1068,19 +1098,22 @@ public class AutomaticBrightnessControllerTest {
// t = 0
// Initial lux
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(1200, mController.getAmbientLux(), EPSILON);
// t = 1000
// Lux isn't steady yet
mClock.fastForward(1000);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(1200, mController.getAmbientLux(), EPSILON);
// t = 2500
// Lux is steady now
mClock.fastForward(1500);
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500,
+ (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER));
assertEquals(500, mController.getAmbientLux(), EPSILON);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index e0e44252a8f3..c151732cec66 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -984,8 +984,7 @@ public class DisplayManagerServiceTest {
Handler handler = displayManager.getDisplayHandler();
waitForIdleHandler(handler);
- assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED,
- EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+ assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED);
}
/**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index aed1f9858660..db94958f769e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1066,7 +1066,6 @@ public final class DisplayPowerControllerTest {
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
- when(mDisplayManagerFlagsMock.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
@@ -1172,7 +1171,6 @@ public final class DisplayPowerControllerTest {
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
- when(mDisplayManagerFlagsMock.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(false);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 2ebb6c2a3ce4..ef39167dbabc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -240,7 +240,6 @@ public final class DisplayBrightnessStrategySelectorTest {
@Test
public void selectStrategyDoesNotSelectDozeStrategyWhenOffloadSessionAutoBrightnessIsEnabled() {
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
@@ -378,7 +377,6 @@ public final class DisplayBrightnessStrategySelectorTest {
@Test
public void selectStrategy_selectsAutomaticStrategyWhenValid() {
when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
@@ -409,7 +407,6 @@ public final class DisplayBrightnessStrategySelectorTest {
@Test
public void selectStrategy_doesNotSelectAutomaticStrategyWhenStylusInUse() {
when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
@@ -536,7 +533,6 @@ public final class DisplayBrightnessStrategySelectorTest {
@Test
public void setAllowAutoBrightnessWhileDozing_enabledWhenConfigAndOffloadSessionAreEnabled() {
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
@@ -550,7 +546,6 @@ public final class DisplayBrightnessStrategySelectorTest {
@Test
public void setAllowAutoBrightnessWhileDozing_disabledWhenOffloadSessionFlagIsDisabled() {
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(false);
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
@@ -564,7 +559,6 @@ public final class DisplayBrightnessStrategySelectorTest {
@Test
public void setAllowAutoBrightnessWhileDozing_disabledWhenABWhileDozingConfigIsDisabled() {
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
@@ -588,7 +582,6 @@ public final class DisplayBrightnessStrategySelectorTest {
@Test
public void setAllowAutoBrightnessWhileDozing_EnabledWhenFlagsAreDisabled() {
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
true);
mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
@@ -600,11 +593,5 @@ public final class DisplayBrightnessStrategySelectorTest {
mDisplayBrightnessStrategySelector
.setAllowAutoBrightnessWhileDozing(mDisplayOffloadSession);
assertTrue(mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozing());
-
- when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true);
- when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(false);
- mDisplayBrightnessStrategySelector
- .setAllowAutoBrightnessWhileDozing(mDisplayOffloadSession);
- assertTrue(mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozing());
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index fa5847560782..4b53f1309337 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -23,18 +23,11 @@ import static com.android.server.am.ActivityManagerService.Injector;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.FrozenProcessListener;
import android.content.ComponentName;
import android.content.Context;
@@ -44,14 +37,12 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.MessageQueue;
import android.os.Process;
-import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.modules.utils.testing.TestableDeviceConfig;
import com.android.server.LocalServices;
@@ -68,11 +59,9 @@ import org.mockito.Mock;
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -164,7 +153,7 @@ public final class CachedAppOptimizerTest {
app.info.uid = packageUid;
// Exact value does not mater, it can be any state for which compaction is allowed.
app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- app.mState.setSetAdj(899);
+ app.mState.setSetAdj(940);
app.mState.setCurAdj(940);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1ef758cf192e..340115a7d465 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -3337,6 +3337,108 @@ public class MockingOomAdjusterTests {
followUpTimeCaptor.capture());
}
+ /**
+ * For Perceptible Tasks adjustment, this solely unit-tests OomAdjuster -> onOtherActivity()
+ */
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS)
+ public void testPerceptibleAdjustment() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+
+ long now = mInjector.getUptimeMillis();
+
+ // GIVEN: perceptible adjustment is NOT enabled (perceptible stop time is not set)
+ // EXPECT: zero adjustment
+ // TLDR: App is not set as a perceptible task and hence no oom_adj boosting.
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+ false, false, PROCESS_STATE_CACHED_ACTIVITY,
+ SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(-1);
+ assertEquals(CACHED_APP_MIN_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+
+ // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and
+ // elapsed time < PERCEPTIBLE_TASK_TIMEOUT
+ // EXPECT: adjustment to PERCEPTIBLE_MEDIUM_APP_ADJ
+ // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting.
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+ false, false, PROCESS_STATE_CACHED_ACTIVITY,
+ SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mInjector.reset();
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(now);
+ assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ,
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+
+ // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and
+ // elapsed time > PERCEPTIBLE_TASK_TIMEOUT
+ // EXPECT: adjustment to PREVIOUS_APP_ADJ
+ // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting, but
+ // time has elapsed and has dropped to a lower boosting of PREVIOUS_APP_ADJ
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+ false, false, PROCESS_STATE_CACHED_ACTIVITY,
+ SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 1000);
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(0);
+ assertEquals(PREVIOUS_APP_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+ }
+
+ /**
+ * For Perceptible Tasks adjustment, this tests overall adjustment flow.
+ */
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS)
+ public void testUpdateOomAdjPerceptible() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ WindowProcessController wpc = app.getWindowProcessController();
+
+ // Set uptime to be at least the timeout time + buffer, so that we don't end up with
+ // negative stopTime in our test input
+ mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 60L * 1000L);
+ long now = mInjector.getUptimeMillis();
+ doReturn(true).when(wpc).hasActivities();
+
+ // GIVEN: perceptible adjustment is is enabled
+ // EXPECT: perceptible-act adjustment
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+ .when(wpc).getActivityStateFlags();
+ doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_IMPORTANT_BACKGROUND, PERCEPTIBLE_MEDIUM_APP_ADJ,
+ SCHED_GROUP_BACKGROUND, "perceptible-act");
+
+ // GIVEN: perceptible adjustment is is enabled and timeout has been reached
+ // EXPECT: stale-perceptible-act adjustment
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+ .when(wpc).getActivityStateFlags();
+
+ doReturn(now - OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS).when(
+ wpc).getPerceptibleTaskStoppedTimeMillis();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ SCHED_GROUP_BACKGROUND, "stale-perceptible-act");
+
+ // GIVEN: perceptible adjustment is is disabled
+ // EXPECT: no perceptible adjustment
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+ .when(wpc).getActivityStateFlags();
+ doReturn(Long.MIN_VALUE).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_CACHED_ACTIVITY, CACHED_APP_MIN_ADJ,
+ SCHED_GROUP_BACKGROUND, "cch-act");
+
+ // GIVEN: perceptible app is in foreground
+ // EXPECT: no perceptible adjustment
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE)
+ .when(wpc).getActivityStateFlags();
+ doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT, "vis-activity");
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testUpdateOomAdj_DoAll_Multiple_Provider_Retention() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
index 904545bd3cc3..b4e845171a0b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
@@ -276,6 +276,7 @@ public class JobServiceContextTest {
final int jobId = 123;
mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
doReturn(jobId).when(mMockJobStatus).getJobId();
mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
@@ -296,7 +297,25 @@ public class JobServiceContextTest {
mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+ verify(mMockJobStatus, never()).setAbandoned(true);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(jobId).when(mMockJobStatus).getJobId();
+
+ mJobServiceContext.mVerb = JobServiceContext.VERB_BINDING;
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+ verify(mMockJobStatus, never()).setAbandoned(true);
+ mJobServiceContext.mVerb = JobServiceContext.VERB_STARTING;
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+ verify(mMockJobStatus, never()).setAbandoned(true);
+
+ mJobServiceContext.mVerb = JobServiceContext.VERB_STOPPING;
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+ verify(mMockJobStatus, never()).setAbandoned(true);
+
+ mJobServiceContext.mVerb = JobServiceContext.VERB_FINISHED;
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
verify(mMockJobStatus, never()).setAbandoned(true);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index c6870adb8464..4e4b3df3c935 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -295,15 +295,15 @@ public class QuotaControllerTest {
}
private void setCharging() {
- doReturn(true).when(mJobSchedulerService).isBatteryCharging();
synchronized (mQuotaController.mLock) {
+ doReturn(true).when(mJobSchedulerService).isBatteryCharging();
mQuotaController.onBatteryStateChangedLocked();
}
}
private void setDischarging() {
- doReturn(false).when(mJobSchedulerService).isBatteryCharging();
synchronized (mQuotaController.mLock) {
+ doReturn(false).when(mJobSchedulerService).isBatteryCharging();
mQuotaController.onBatteryStateChangedLocked();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java
index 8fc8c9f677a6..6be9c6d4b80c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java
@@ -48,6 +48,7 @@ import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
@@ -121,7 +122,7 @@ public class BatteryUsageStatsProviderPerfTest {
}
@Test
- public void getBatteryUsageStats_accumulated() {
+ public void getBatteryUsageStats_accumulated() throws IOException {
BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
.setMaxStatsAgeMs(0)
.includePowerStateData()
@@ -155,6 +156,8 @@ public class BatteryUsageStatsProviderPerfTest {
// Verify that all iterations produce the same result
assertThat(cpuConsumedPower).isEqualTo(expectedCpuPower);
}
+ stats.close();
+
state.resumeTiming();
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
index cb9d9b12a2fc..dcddf06f01fb 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
@@ -34,6 +34,7 @@ import static org.junit.Assert.fail;
import android.os.BatteryConsumer;
import android.os.PersistableBundle;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.IntArray;
import android.util.LongArray;
import androidx.test.filters.SmallTest;
@@ -51,7 +52,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -315,8 +315,12 @@ public class CpuPowerStatsProcessorTest {
}
@Override
- void collectUids(Collection<Integer> uids) {
- uids.addAll(mUids);
+ IntArray getActiveUids() {
+ IntArray uids = new IntArray();
+ for (Integer uid : mUids) {
+ uids.add(uid);
+ }
+ return uids;
}
void verifyPowerEstimates() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
index a232c0c7aec9..3b614bdb38ed 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
@@ -143,6 +143,8 @@ public class MultiStateStatsTest {
multiStateStats.increment(new long[]{200, 200}, 5000);
+ multiStateStats.increment(null, 6000); // No-op
+
long[] stats = new long[DIMENSION_COUNT];
multiStateStats.getStats(stats, new int[]{0, BatteryConsumer.PROCESS_STATE_FOREGROUND, 0});
// (400 - 100) * 0.5 + (600 - 400) * 0.5
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index d702cae248a9..009ce88cfd6c 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -33,6 +33,9 @@ android_test {
"test-apps/DisplayManagerTestApp/src/**/*.java",
],
+ kotlincflags: [
+ "-Werror",
+ ],
static_libs: [
"a11ychecker",
"aatf",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 834fea46e505..4531b3948495 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -199,6 +199,10 @@
<service android:name="com.android.server.job.MockBiasJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
+ <activity
+ android:name="android.app.Activity"
+ android:exported="false" />
+
<activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity"/>
<activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity2"/>
<activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity3"/>
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 7f60caaa569b..0745c68fd337 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.platform.test.annotations.DisableFlags;
@@ -400,6 +401,46 @@ public class AutoclickControllerTest {
.isNotEqualTo(initialScheduledTime);
}
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void pauseButton_flagOn_clickNotTriggeredWhenPaused() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Pause autoclick.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ // Send hover move event.
+ MotionEvent hoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+ // Verify there is not a pending click.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+
+ // Resume autoclick.
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(false);
+
+ // Send initial move event again. Because this is the first recorded move, a click won't be
+ // scheduled.
+ injectFakeMouseActionHoverMoveEvent();
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+
+ // Send move again to trigger click and verify there is now a pending click.
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+ }
+
private void injectFakeMouseActionHoverMoveEvent() {
MotionEvent event = getFakeMotionHoverMoveEvent();
event.setSource(InputDevice.SOURCE_MOUSE);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index f3016f4b17f9..ba672dcd299b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -68,6 +68,7 @@ public class AutoclickTypePanelTest {
private LinearLayout mPositionButton;
private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+ private boolean mPaused;
private final ClickPanelControllerInterface clickPanelController =
new ClickPanelControllerInterface() {
@@ -77,7 +78,9 @@ public class AutoclickTypePanelTest {
}
@Override
- public void toggleAutoclickPause() {}
+ public void toggleAutoclickPause(boolean paused) {
+ mPaused = paused;
+ }
};
@Before
@@ -199,6 +202,17 @@ public class AutoclickTypePanelTest {
}
}
+ @Test
+ public void pauseButton_onClick() {
+ mPauseButton.callOnClick();
+ assertThat(mPaused).isTrue();
+ assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+ mPauseButton.callOnClick();
+ assertThat(mPaused).isFalse();
+ assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+ }
+
private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
assertThat(gradientDrawable.getColor().getDefaultColor())
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index e0023e59af50..30aa8cebdff6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1768,7 +1768,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
- @Ignore // b/396073342
public void testCertificateDisclosure() throws Exception {
final int userId = CALLER_USER_HANDLE;
final UserHandle user = UserHandle.of(userId);
@@ -4613,7 +4612,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
- @Ignore // b/396073342
public void testGetLastBugReportRequestTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -4661,7 +4659,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
- @Ignore // b/396073342
public void testGetLastNetworkLogRetrievalTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -6444,7 +6441,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
- @Ignore // b/396073342
public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
mServiceContext.packageName = mRealTestContext.getPackageName();
mServiceContext.applicationInfo = new ApplicationInfo();
@@ -6456,7 +6452,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
- @Ignore // b/396073342
public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
mServiceContext.packageName = mRealTestContext.getPackageName();
mServiceContext.applicationInfo = new ApplicationInfo();
@@ -6469,7 +6464,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
- @Ignore // b/396073342
public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
mServiceContext.packageName = mRealTestContext.getPackageName();
mServiceContext.applicationInfo = new ApplicationInfo();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index e5fac7ac5e0c..00b0c558b4e3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -263,6 +263,11 @@ public class DpmMockContext extends MockContext {
}
@Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mMockSystemServices.packageManager;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 87c9db2fe565..acbce36c3d7f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -354,14 +354,20 @@ public abstract class BaseLockSettingsServiceTests {
@After
public void tearDown_baseServices() throws Exception {
- mStorage.closeDatabase();
+ if (mStorage != null) {
+ mStorage.closeDatabase();
+ }
File db = InstrumentationRegistry.getContext().getDatabasePath("locksettings.db");
assertTrue(!db.exists() || db.delete());
- File storageDir = mStorage.mStorageDir;
- assertTrue(FileUtils.deleteContents(storageDir));
+ if (mStorage != null) {
+ File storageDir = mStorage.mStorageDir;
+ assertTrue(FileUtils.deleteContents(storageDir));
+ }
- mPasswordSlotManager.cleanup();
+ if (mPasswordSlotManager != null) {
+ mPasswordSlotManager.cleanup();
+ }
}
protected void flushHandlerTasks() {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 02b86db6ab6f..387b89a41eba 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -124,7 +124,9 @@ public class LockSettingsStorageTests {
@After
public void tearDown() throws Exception {
- mStorage.closeDatabase();
+ if (mStorage != null) {
+ mStorage.closeDatabase();
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
index 2faf6a2b29d1..2c2b9374fdf9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
@@ -49,7 +49,9 @@ public class PasswordSlotManagerTests {
@After
public void tearDown() throws Exception {
- mManager.cleanup();
+ if (mManager != null) {
+ mManager.cleanup();
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index 1514de04fb08..5add74e5b69e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -156,9 +156,12 @@ public class KeySyncTaskTest {
@After
public void tearDown() {
- mRecoverableKeyStoreDb.close();
- mDatabaseFile.delete();
-
+ if (mRecoverableKeyStoreDb != null) {
+ mRecoverableKeyStoreDb.close();
+ }
+ if (mDatabaseFile != null) {
+ mDatabaseFile.delete();
+ }
File file = new File(InstrumentationRegistry.getTargetContext().getFilesDir(),
SNAPSHOT_TOP_LEVEL_DIRECTORY);
FileUtils.deleteContentsAndDir(file);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index c09e09c8404f..46eaba7dace6 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -117,8 +117,12 @@ public class PlatformKeyManagerTest {
@After
public void tearDown() {
- mRecoverableKeyStoreDb.close();
- mDatabaseFile.delete();
+ if (mRecoverableKeyStoreDb != null) {
+ mRecoverableKeyStoreDb.close();
+ }
+ if (mDatabaseFile != null) {
+ mDatabaseFile.delete();
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index 64130266b2c4..e6a6e36e75d6 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -89,8 +89,12 @@ public class RecoverableKeyGeneratorTest {
keyStore.load(/*param=*/ null);
keyStore.deleteEntry(WRAPPING_KEY_ALIAS);
- mRecoverableKeyStoreDb.close();
- mDatabaseFile.delete();
+ if (mRecoverableKeyStoreDb != null) {
+ mRecoverableKeyStoreDb.close();
+ }
+ if (mDatabaseFile != null) {
+ mDatabaseFile.delete();
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 7641fb957cc8..878c838e734b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -230,9 +230,15 @@ public class RecoverableKeyStoreManagerTest {
@After
public void tearDown() {
- mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
- mRecoverableKeyStoreDb.close();
- mDatabaseFile.delete();
+ if (mRemoteLockscreenValidationSessionStorage != null) {
+ mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
+ }
+ if (mRecoverableKeyStoreDb != null) {
+ mRecoverableKeyStoreDb.close();
+ }
+ if (mDatabaseFile != null) {
+ mDatabaseFile.delete();
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
index bbd9223718ae..fb98fab52ca0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
@@ -18,6 +18,8 @@ package com.android.server.locksettings.recoverablekeystore.storage;
import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
@@ -36,8 +38,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RecoverableKeyStoreDbHelperTest {
@@ -110,7 +110,9 @@ public class RecoverableKeyStoreDbHelperTest {
@After
public void tearDown() throws Exception {
- mDatabase.close();
+ if (mDatabase != null) {
+ mDatabase.close();
+ }
}
private void createV2Tables() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 8bc14fc54ae1..a77d8bcd3875 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -72,8 +72,12 @@ public class RecoverableKeyStoreDbTest {
@After
public void tearDown() {
- mRecoverableKeyStoreDb.close();
- mDatabaseFile.delete();
+ if (mRecoverableKeyStoreDb != null) {
+ mRecoverableKeyStoreDb.close();
+ }
+ if (mDatabaseFile != null) {
+ mDatabaseFile.delete();
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java
index b2e296a36b93..2912a0762761 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java
@@ -19,6 +19,7 @@ package com.android.server.om;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.content.om.OverlayConstraint.TYPE_DEVICE_ID;
import static android.content.om.OverlayConstraint.TYPE_DISPLAY_ID;
+import static android.util.TypedValue.TYPE_STRING;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
@@ -28,6 +29,9 @@ import static junit.framework.Assert.assertNotNull;
import static org.testng.Assert.assertThrows;
+import android.app.Activity;
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
import android.content.om.FabricatedOverlay;
import android.content.om.OverlayConstraint;
import android.content.om.OverlayIdentifier;
@@ -35,12 +39,12 @@ import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
import android.content.om.OverlayManagerTransaction;
import android.content.res.Flags;
+import android.content.res.Resources;
import android.os.UserHandle;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.util.TypedValue;
+import android.view.Display;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
@@ -53,20 +57,28 @@ import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeoutException;
@RunWith(JUnitParamsRunner.class)
public class OverlayConstraintsTests {
+ private static final String RESOURCE_NAME = "string/module_2_name";
+ private static final String RESOURCE_DEFAULT_VALUE = "module_2_name";
+ private static final String RESOURCE_OVERLAID_VALUE = "hello";
+ private static final long TIMEOUT_MILLIS = 2000L;
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private OverlayManager mOverlayManager;
private UserHandle mUserHandle;
private OverlayIdentifier mOverlayIdentifier = null;
+ private final String mPackageName = getApplicationContext().getPackageName();
@Before
public void setUp() throws Exception {
- mOverlayManager = getApplicationContext().getSystemService(OverlayManager.class);
+ final Context context = getApplicationContext();
+ mOverlayManager = context.getSystemService(OverlayManager.class);
mUserHandle = UserHandle.of(UserHandle.myUserId());
}
@@ -79,6 +91,7 @@ public class OverlayConstraintsTests {
.build();
mOverlayManager.commit(transaction);
mOverlayIdentifier = null;
+ waitForResourceValue(RESOURCE_DEFAULT_VALUE, getApplicationContext());
}
}
@@ -161,13 +174,161 @@ public class OverlayConstraintsTests {
List.of(new OverlayConstraint(TYPE_DISPLAY_ID, DEFAULT_DISPLAY))));
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS)
+ public void enableOverlayWithoutConstraints_appliesOverlayWithoutConstraints()
+ throws Exception {
+ enableOverlay(Collections.emptyList());
+
+ // Assert than the overlay is applied for both default device context and virtual
+ // device context.
+ final Context context = getApplicationContext();
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, context);
+ VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice();
+ final Context deviceContext = context.createDeviceContext(virtualDevice.getDeviceId());
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, deviceContext);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS)
+ public void enableOverlayWithConstraints_withTypeDeviceId_appliesOverlayWithConstraints()
+ throws Exception {
+ final int deviceId1 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId();
+ final int deviceId2 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId();
+ enableOverlay(List.of(new OverlayConstraint(TYPE_DEVICE_ID, deviceId1),
+ new OverlayConstraint(TYPE_DEVICE_ID, deviceId2)));
+
+ // Assert than the overlay is not applied for contexts not associated with the above
+ // devices.
+ final Context context = getApplicationContext();
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context);
+ final int deviceId3 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId();
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context.createDeviceContext(deviceId3));
+
+ // Assert than the overlay is applied for contexts associated with the above devices.
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDeviceContext(deviceId1));
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDeviceContext(deviceId2));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS)
+ public void enableOverlayWithConstraints_withTypeDisplayId_appliesOverlayWithConstraints()
+ throws Exception {
+ final Display display1 =
+ mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay();
+ final Display display2 =
+ mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay();
+ enableOverlay(List.of(new OverlayConstraint(TYPE_DISPLAY_ID, display1.getDisplayId()),
+ new OverlayConstraint(TYPE_DISPLAY_ID, display2.getDisplayId())));
+
+ // Assert than the overlay is not applied for contexts not associated with the above
+ // displays.
+ final Context context = getApplicationContext();
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context);
+ final Display display3 =
+ mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay();
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context.createDisplayContext(display3));
+
+ // Assert than the overlay is applied for contexts associated with the above displays.
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDisplayContext(display1));
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDisplayContext(display2));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS)
+ public void enableOverlayWithConstraints_withTypesDisplayIdAndDeviceId_appliesOverlayWithConstraints()
+ throws Exception {
+ final Display display1 =
+ mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay();
+ final int deviceId1 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId();
+ enableOverlay(List.of(new OverlayConstraint(TYPE_DISPLAY_ID, display1.getDisplayId()),
+ new OverlayConstraint(TYPE_DEVICE_ID, deviceId1)));
+
+ // Assert than the overlay is not applied for contexts not associated with the above
+ // display or device.
+ final Context context = getApplicationContext();
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context);
+ final Display display2 =
+ mVirtualDeviceRule.createManagedUnownedVirtualDisplay().getDisplay();
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context.createDisplayContext(display2));
+ final int deviceId2 = mVirtualDeviceRule.createManagedVirtualDevice().getDeviceId();
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, context.createDeviceContext(deviceId2));
+
+ // Assert than the overlay is applied for contexts associated with the above display or
+ // device.
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDisplayContext(display1));
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, context.createDeviceContext(deviceId1));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS)
+ public void enableOverlayWithConstraints_withTypeDisplayId_appliesForActivityOnDisplay()
+ throws Exception {
+ final Display display =
+ mVirtualDeviceRule.createManagedUnownedVirtualDisplay(
+ VirtualDeviceRule.createTrustedVirtualDisplayConfigBuilder())
+ .getDisplay();
+ final Activity activityOnDefaultDisplay = mVirtualDeviceRule.startActivityOnDisplaySync(
+ DEFAULT_DISPLAY, Activity.class);
+ final Activity activityOnVirtualDisplay = mVirtualDeviceRule.startActivityOnDisplaySync(
+ display.getDisplayId(), Activity.class);
+
+ enableOverlay(List.of(new OverlayConstraint(TYPE_DISPLAY_ID, display.getDisplayId())));
+
+ // Assert than the overlay is not applied for any existing activity on the default display.
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, activityOnDefaultDisplay);
+ // Assert than the overlay is applied for any existing activity on the virtual display.
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, activityOnVirtualDisplay);
+
+ // Assert than the overlay is not applied for any new activity on the default display.
+ final Activity newActivityOnDefaultDisplay = mVirtualDeviceRule.startActivityOnDisplaySync(
+ DEFAULT_DISPLAY, Activity.class);
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, newActivityOnDefaultDisplay);
+ // Assert than the overlay is applied for any new activity on the virtual display.
+ final Activity newActivityOnVirtualDisplay = mVirtualDeviceRule.startActivityOnDisplaySync(
+ display.getDisplayId(), Activity.class);
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, newActivityOnVirtualDisplay);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS)
+ public void enableOverlayWithConstraints_withTypeDeviceId_appliesForActivityOnDevice()
+ throws Exception {
+ final VirtualDeviceManager.VirtualDevice device =
+ mVirtualDeviceRule.createManagedVirtualDevice();
+ final Display display =
+ mVirtualDeviceRule.createManagedVirtualDisplay(device,
+ VirtualDeviceRule.createTrustedVirtualDisplayConfigBuilder())
+ .getDisplay();
+ final Activity activityOnDefaultDevice = mVirtualDeviceRule.startActivityOnDisplaySync(
+ DEFAULT_DISPLAY, Activity.class);
+ final Activity activityOnVirtualDevice = mVirtualDeviceRule.startActivityOnDisplaySync(
+ display.getDisplayId(), Activity.class);
+
+ enableOverlay(List.of(new OverlayConstraint(TYPE_DEVICE_ID, device.getDeviceId())));
+
+ // Assert than the overlay is not applied for any existing activity on the default device.
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, activityOnDefaultDevice);
+ // Assert than the overlay is applied for any existing activity on the virtual device.
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, activityOnVirtualDevice);
+
+ // Assert than the overlay is not applied for any new activity on the default device.
+ final Activity newActivityOnDefaultDevice = mVirtualDeviceRule.startActivityOnDisplaySync(
+ DEFAULT_DISPLAY, Activity.class);
+ ensureResourceValueStaysAt(RESOURCE_DEFAULT_VALUE, newActivityOnDefaultDevice);
+ // Assert than the overlay is applied for any new activity on the virtual device.
+ final Activity newActivityOnVirtualDevice = mVirtualDeviceRule.startActivityOnDisplaySync(
+ display.getDisplayId(), Activity.class);
+ waitForResourceValue(RESOURCE_OVERLAID_VALUE, newActivityOnVirtualDevice);
+ }
+
private FabricatedOverlay createFabricatedOverlay() {
- String packageName = getApplicationContext().getPackageName();
FabricatedOverlay fabricatedOverlay = new FabricatedOverlay.Builder(
- packageName, "testOverlay" /* name */, packageName)
+ mPackageName, "testOverlay" /* name */, mPackageName)
.build();
- fabricatedOverlay.setResourceValue("string/module_2_name" /* resourceName */,
- TypedValue.TYPE_STRING, "hello" /* value */, null /* configuration */);
+ fabricatedOverlay.setResourceValue(RESOURCE_NAME, TYPE_STRING, RESOURCE_OVERLAID_VALUE,
+ null /* configuration */);
return fabricatedOverlay;
}
@@ -183,6 +344,37 @@ public class OverlayConstraintsTests {
mOverlayIdentifier = fabricatedOverlay.getIdentifier();
}
+ private static void waitForResourceValue(final String expectedValue, Context context)
+ throws TimeoutException {
+ final long endTime = System.currentTimeMillis() + TIMEOUT_MILLIS;
+ final Resources resources = context.getResources();
+ final int resourceId = getResourceId(context);
+ String resourceValue = null;
+ while (System.currentTimeMillis() < endTime) {
+ resourceValue = resources.getString(resourceId);
+ if (Objects.equals(resourceValue, expectedValue)) {
+ return;
+ }
+ }
+ throw new TimeoutException("Timed out waiting for '" + RESOURCE_NAME + "' value to equal '"
+ + expectedValue + "': current value is '" + resourceValue + "'");
+ }
+
+ private static void ensureResourceValueStaysAt(final String expectedValue, Context context) {
+ final long endTime = System.currentTimeMillis() + TIMEOUT_MILLIS;
+ final Resources resources = context.getResources();
+ final int resourceId = getResourceId(context);
+ String resourceValue;
+ while (System.currentTimeMillis() < endTime) {
+ resourceValue = resources.getString(resourceId);
+ assertEquals(expectedValue, resourceValue);
+ }
+ }
+
+ private static int getResourceId(Context context) {
+ return context.getResources().getIdentifier(RESOURCE_NAME, "", context.getPackageName());
+ }
+
private static List<OverlayConstraint>[] getAllConstraintLists() {
return new List[]{
Collections.emptyList(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5dea44d6ebf4..67e85ff2445d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -4495,6 +4495,27 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void testBubblePreference_sameVersionWithSAWPermission() throws Exception {
+ when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+ anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+
+ final String xml = "<ranking version=\"4\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+ + "<channel id=\"someId\" name=\"hi\""
+ + " importance=\"3\"/>"
+ + "</package>"
+ + "</ranking>";
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O));
+ assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+ }
+
+ @Test
public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception {
when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index d5548a4f375e..f091a65cfe46 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -497,19 +497,19 @@ public class DeviceAdapterTest {
private VibratorController createEmptyVibratorController(int vibratorId) {
return new FakeVibratorControllerProvider(mTestLooper.getLooper())
- .newVibratorController(vibratorId, (id, vibrationId) -> {});
+ .newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private VibratorController createBasicVibratorController(int vibratorId) {
FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
IVibrator.CAP_COMPOSE_EFFECTS);
- return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private VibratorController createPwleWithoutFrequenciesVibratorController(int vibratorId) {
FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
- return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private VibratorController createPwleVibratorController(int vibratorId) {
@@ -519,7 +519,7 @@ public class DeviceAdapterTest {
provider.setMinFrequency(TEST_MIN_FREQUENCY);
provider.setFrequencyResolution(TEST_FREQUENCY_RESOLUTION);
provider.setMaxAmplitudes(TEST_AMPLITUDE_MAP);
- return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private VibratorController createPwleV2VibratorController(int vibratorId) {
@@ -538,7 +538,7 @@ public class DeviceAdapterTest {
provider.setMinEnvelopeEffectControlPointDurationMillis(
TEST_MIN_ENVELOPE_EFFECT_CONTROL_POINT_DURATION_MILLIS);
- return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private FakeVibratorControllerProvider createVibratorProviderWithEffects(int... capabilities) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 5f2af0a085c3..42279e40fa33 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -17,6 +17,9 @@
package com.android.server.vibrator;
import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -66,7 +69,6 @@ import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -153,9 +155,10 @@ public class VibrationThreadTest {
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
.thenReturn(new ComponentName("", ""));
doAnswer(answer -> {
- mVibrationConductor.notifyVibratorComplete(answer.getArgument(0));
+ mVibrationConductor.notifyVibratorComplete(
+ answer.getArgument(0), answer.getArgument(2));
return null;
- }).when(mControllerCallbacks).onComplete(anyInt(), anyLong());
+ }).when(mControllerCallbacks).onComplete(anyInt(), anyLong(), anyLong());
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
@@ -190,7 +193,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id));
+ verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
}
@@ -203,7 +206,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id));
+ verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
}
@@ -217,7 +220,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -234,7 +237,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -254,7 +257,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -265,7 +268,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
// No user settings scale.
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
@@ -291,7 +294,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
// No user settings scale.
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
@@ -412,6 +415,7 @@ public class VibrationThreadTest {
.isEqualTo(expectedOneShots(200L, 50L));
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@LargeTest
@Test
public void vibrate_singleVibratorRepeatingPatternWithZeroDurationSteps_repeatsEffectCorrectly()
@@ -443,6 +447,30 @@ public class VibrationThreadTest {
.isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L));
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
+ @Test
+ public void vibrate_singleVibratorPatternWithCallbackDelay_oldCallbacksIgnored() {
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+ fakeVibrator.setCompletionCallbackDelay(100); // 100ms delay to notify service.
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[]{0, 200, 50, 400}, /* repeat= */ -1);
+ HalVibration vibration = startThreadAndDispatcher(effect);
+ waitForCompletion(800 + TEST_TIMEOUT_MILLIS); // 200 + 50 + 400 + 100ms delay
+
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
+ assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), eq(1L));
+ // Step id = 2 skipped by the 50ms OFF step after the 200ms ON step.
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), eq(3L));
+
+ // First callback ignored, did not cause the vibrator to turn back on during the 400ms step.
+ assertThat(fakeVibrator.getEffectSegments(vibration.id))
+ .isEqualTo(expectedOneShots(200L, 400L));
+ }
+
@Test
public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -479,12 +507,12 @@ public class VibrationThreadTest {
throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK);
fakeVibrator.setCompositionSizeMax(10);
VibrationEffect effect = VibrationEffect.startComposition()
// Very long delay so thread will be cancelled after first PWLE is triggered.
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
.compose();
VibrationEffect repeatingEffect = VibrationEffect.startComposition()
.repeatEffectIndefinitely(effect)
@@ -561,13 +589,12 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
@@ -591,7 +618,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_singleVibratorVendorEffectCancel_cancelsVibrationImmediately()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -657,7 +684,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -678,7 +705,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -695,13 +722,14 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, never())
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_singleVibratorVendorEffect_runsVibration() {
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -712,7 +740,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID),
eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
@@ -725,24 +753,23 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorComposed_runsVibration() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_TICK, 0.5f)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
+ expectedPrimitive(PRIMITIVE_CLICK, 1, 0),
+ expectedPrimitive(PRIMITIVE_TICK, 0.5f, 0)),
fakeVibrator.getEffectSegments(vibration.id));
}
@@ -750,14 +777,15 @@ public class VibrationThreadTest {
@DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
public void vibrate_singleVibratorComposedAndNoCapability_triggersHalAndReturnsUnsupported() {
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, never())
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
}
@@ -766,14 +794,15 @@ public class VibrationThreadTest {
@EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, never())
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
}
@@ -782,34 +811,30 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- fakeVibrator.setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK,
- VibrationEffect.Composition.PRIMITIVE_SPIN);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_SPIN);
fakeVibrator.setCompositionSizeMax(2);
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_TICK, 0.5f)
+ .addPrimitive(PRIMITIVE_SPIN, 0.8f)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verifyCallbacksTriggered(vibration, Status.FINISHED);
// Vibrator compose called twice.
- verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(2))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertEquals(3, fakeVibrator.getEffectSegments(vibration.id).size());
}
@Test
- @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- fakeVibrator.setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
fakeVibrator.setMinFrequency(100);
@@ -820,8 +845,8 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.startComposition()
.addEffect(VibrationEffect.createOneShot(10, 100))
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_TICK, 0.5f)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addEffect(VibrationEffect.startWaveform()
.addTransition(Duration.ofMillis(10),
@@ -836,13 +861,14 @@ public class VibrationThreadTest {
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(5))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedOneShot(10),
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
+ expectedPrimitive(PRIMITIVE_CLICK, 1, 0),
+ expectedPrimitive(PRIMITIVE_TICK, 0.5f, 0),
expectedPrebaked(VibrationEffect.EFFECT_CLICK),
expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
/* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
@@ -857,17 +883,15 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorComposedWithFallback_replacedInTheMiddleOfComposition() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- fakeVibrator.setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
VibrationEffect effect = VibrationEffect.startComposition()
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .addPrimitive(PRIMITIVE_TICK, 0.5f)
.compose();
HalVibration vibration = createVibration(CombinedVibration.createParallel(effect));
vibration.fillFallbacks(unused -> fallback);
@@ -877,7 +901,8 @@ public class VibrationThreadTest {
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(4))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -894,7 +919,7 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_runsComposePwleV2() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -915,7 +940,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -929,7 +954,7 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorBasicPwle_runsComposePwleV2() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -951,7 +976,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(220L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -964,7 +989,7 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_withInitialFrequency_runsComposePwleV2() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -987,7 +1012,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -1001,7 +1026,7 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_TooManyControlPoints_splitsAndRunsComposePwleV2() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -1027,7 +1052,8 @@ public class VibrationThreadTest {
verifyCallbacksTriggered(vibration, Status.FINISHED);
// Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
// Using best split points instead of max-packing PWLEs.
- verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(3))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -1043,7 +1069,7 @@ public class VibrationThreadTest {
}
@Test
- @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_runsComposePwle() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
@@ -1066,7 +1092,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -1109,7 +1135,8 @@ public class VibrationThreadTest {
// Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
// Using best split points instead of max-packing PWLEs.
- verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(3))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertEquals(6, fakeVibrator.getEffectSegments(vibration.id).size());
}
@@ -1160,8 +1187,8 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
- verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -1183,9 +1210,9 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
@@ -1207,11 +1234,10 @@ public class VibrationThreadTest {
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(4).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(4).setSupportedPrimitives(PRIMITIVE_CLICK);
VibrationEffect composed = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
@@ -1225,10 +1251,10 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
@@ -1243,8 +1269,7 @@ public class VibrationThreadTest {
assertEquals(Arrays.asList(expectedOneShot(20)),
mVibratorProviders.get(3).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
- assertEquals(Arrays.asList(
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(4).getEffectSegments(vibration.id));
}
@@ -1253,12 +1278,11 @@ public class VibrationThreadTest {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(2).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibrationEffect composed = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startSequential()
.addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 50)
@@ -1268,10 +1292,10 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- InOrder controllerVerifier = inOrder(mControllerCallbacks);
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
+ InOrder verifier = inOrder(mControllerCallbacks);
+ verifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
+ verifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+ verifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
InOrder batteryVerifier = inOrder(mManagerHooks);
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
@@ -1289,8 +1313,7 @@ public class VibrationThreadTest {
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(3).getEffectSegments(vibration.id));
@@ -1301,15 +1324,13 @@ public class VibrationThreadTest {
int[] vibratorIds = new int[]{1, 2};
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(1).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(1).setSupportedPrimitives(PRIMITIVE_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(2).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
VibrationEffect composed = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1, 100)
.compose();
CombinedVibration effect = CombinedVibration.createParallel(composed);
// We create the HalVibration here to obtain the vibration id and use it to mock the
@@ -1331,8 +1352,7 @@ public class VibrationThreadTest {
verify(mManagerHooks, never()).cancelSyncedVibration();
verifyCallbacksTriggered(vibration, Status.FINISHED);
- VibrationEffectSegment expected = expectedPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+ VibrationEffectSegment expected = expectedPrimitive(PRIMITIVE_CLICK, 1, 100);
assertEquals(Arrays.asList(expected),
mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expected),
@@ -1345,12 +1365,11 @@ public class VibrationThreadTest {
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(4).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(4).setSupportedPrimitives(PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
VibrationEffect composed = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
@@ -1463,9 +1482,9 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
@@ -1505,7 +1524,7 @@ public class VibrationThreadTest {
waitForCompletion(rampDownDuration + TEST_TIMEOUT_MILLIS);
long completionTime = SystemClock.elapsedRealtime();
- verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
// Vibration ends after duration, thread completed after ramp down
assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration);
assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + rampDownDuration);
@@ -1534,7 +1553,7 @@ public class VibrationThreadTest {
waitForCompletion(TEST_TIMEOUT_MILLIS);
- verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackDelay);
}
@@ -1563,7 +1582,8 @@ public class VibrationThreadTest {
waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS);
long completionTime = SystemClock.elapsedRealtime();
- verify(mControllerCallbacks, never()).onComplete(VIBRATOR_ID, vibration.id);
+ verify(mControllerCallbacks, never())
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
// Vibration ends and thread completes after timeout, before the HAL callback
assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackTimeout);
assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + callbackDelay);
@@ -1639,15 +1659,14 @@ public class VibrationThreadTest {
mockVibrators(1, 2);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(2).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addVibrator(2, VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
.compose())
.combine();
HalVibration vibration = startThreadAndDispatcher(effect);
@@ -1672,7 +1691,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_multipleVendorEffectCancel_cancelsVibrationImmediately() throws Exception {
mockVibrators(1, 2);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -1767,7 +1786,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
// Duration extended for 5 + 5 + 5 + 15.
@@ -1847,7 +1866,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
@@ -1856,7 +1875,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -1865,7 +1884,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id))
@@ -1879,26 +1898,24 @@ public class VibrationThreadTest {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
- assertEquals(
- Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@Test
- @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -1916,7 +1933,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
@@ -1928,8 +1945,7 @@ public class VibrationThreadTest {
public void vibrate_multipleVibrations_withCancel() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(
VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
- mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1940,7 +1956,7 @@ public class VibrationThreadTest {
.repeatEffectIndefinitely(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.compose();
VibrationEffect effect3 = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100);
VibrationEffect effect5 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
@@ -1974,14 +1990,15 @@ public class VibrationThreadTest {
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
// Effect1
- verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration1.id);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration1.id), anyLong());
verifyCallbacksTriggered(vibration1, Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
fakeVibrator.getEffectSegments(vibration1.id));
// Effect2: repeating, cancelled.
- verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibration2.id);
+ verify(mControllerCallbacks, atLeast(2))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration2.id), anyLong());
verifyCallbacksTriggered(vibration2, Status.CANCELLED_BY_USER);
// The exact count of segments might vary, so just check that there's more than 2 and
@@ -1994,10 +2011,9 @@ public class VibrationThreadTest {
}
// Effect3
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id), anyLong());
verifyCallbacksTriggered(vibration3, Status.FINISHED);
- assertEquals(Arrays.asList(
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)),
fakeVibrator.getEffectSegments(vibration3.id));
// Effect4: cancelled quickly.
@@ -2014,8 +2030,7 @@ public class VibrationThreadTest {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(2).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
CombinedVibration effect = CombinedVibration.startSequential()
@@ -2029,8 +2044,7 @@ public class VibrationThreadTest {
/* delay= */ TEST_TIMEOUT_MILLIS)
.addNext(2,
VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1,
- /* delay= */ TEST_TIMEOUT_MILLIS)
+ .addPrimitive(PRIMITIVE_CLICK, 1, /* delay= */ TEST_TIMEOUT_MILLIS)
.compose(),
/* delay= */ TEST_TIMEOUT_MILLIS)
.combine();
@@ -2051,8 +2065,7 @@ public class VibrationThreadTest {
assertEquals(Arrays.asList(expectedOneShot(TEST_TIMEOUT_MILLIS)),
mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(255), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(expectedPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)),
mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(3).getEffectSegments(vibration.id));
@@ -2205,7 +2218,7 @@ public class VibrationThreadTest {
private void verifyCallbacksTriggered(HalVibration vibration, Status expectedStatus) {
assertThat(vibration.getStatus()).isEqualTo(expectedStatus);
- verify(mManagerHooks).onVibrationThreadReleased(vibration.id);
+ verify(mManagerHooks).onVibrationThreadReleased(eq(vibration.id));
}
private static final class TestLooperAutoDispatcher extends Thread {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index 0978f48491cc..4df13deaed7d 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -203,9 +203,10 @@ public class VibratorControllerTest {
@Test
public void setAmplitude_vibratorVibrating_setsAmplitude() {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
- controller.on(100, /* vibrationId= */ 1);
+ controller.on(100, 1, 1);
assertTrue(controller.isVibrating());
assertEquals(-1, controller.getCurrentAmplitude(), /* delta= */ 0);
@@ -215,81 +216,84 @@ public class VibratorControllerTest {
@Test
public void on_withDuration_turnsVibratorOn() {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
- controller.on(100, 10);
+ controller.on(100, 10, 20);
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).on(eq(100L), eq(10L));
+ verify(mNativeWrapperMock).on(eq(100L), eq(10L), eq(20L));
}
@Test
public void on_withPrebaked_performsEffect() {
- when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L);
+ when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong(), anyLong()))
+ .thenReturn(10L);
VibratorController controller = createController();
PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_STRENGTH_MEDIUM);
- assertEquals(10L, controller.on(prebaked, 11));
+ assertEquals(10L, controller.on(prebaked, 11, 23));
assertTrue(controller.isVibrating());
verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK),
- eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L));
+ eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L), eq(23L));
}
@Test
public void on_withComposed_performsEffect() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L);
+ when(mNativeWrapperMock.compose(any(), anyLong(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
PrimitiveSegment[] primitives = new PrimitiveSegment[]{
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
};
- assertEquals(15L, controller.on(primitives, 12));
+ assertEquals(15L, controller.on(primitives, 12, 34));
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
+ verify(mNativeWrapperMock).compose(eq(primitives), eq(12L), eq(34L));
}
@Test
public void on_withComposedPwle_performsEffect() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
- when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong())).thenReturn(15L);
+ when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
RampSegment[] primitives = new RampSegment[]{
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
/* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10)
};
- assertEquals(15L, controller.on(primitives, 12));
+ assertEquals(15L, controller.on(primitives, 12, 45));
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L));
+ verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L), eq(45L));
}
@Test
public void on_withComposedPwleV2_performsEffect() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
- when(mNativeWrapperMock.composePwleV2(any(), anyLong())).thenReturn(15L);
+ when(mNativeWrapperMock.composePwleV2(any(), anyLong(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
PwlePoint[] primitives = new PwlePoint[]{
new PwlePoint(/*amplitude=*/ 0, /*frequencyHz=*/ 100, /*timeMillis=*/ 0),
new PwlePoint(/*amplitude=*/ 1, /*frequencyHz=*/ 200, /*timeMillis=*/ 10)
};
- assertEquals(15L, controller.on(primitives, 12));
+ assertEquals(15L, controller.on(primitives, 12, 53));
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L));
+ verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L), eq(53L));
}
@Test
public void off_turnsOffVibrator() {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
- controller.on(100, 1);
+ controller.on(100, 1, 1);
assertTrue(controller.isVibrating());
controller.off();
@@ -301,10 +305,11 @@ public class VibratorControllerTest {
@Test
public void reset_turnsOffVibratorAndDisablesExternalControl() {
mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
- controller.on(100, 1);
+ controller.on(100, 1, 1);
assertTrue(controller.isVibrating());
controller.reset();
@@ -315,12 +320,13 @@ public class VibratorControllerTest {
@Test
public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
controller.registerVibratorStateListener(mVibratorStateListenerMock);
- controller.on(10, 1);
- controller.on(100, 2);
+ controller.on(10, 1, 1);
+ controller.on(100, 2, 1);
controller.off();
controller.off();
@@ -334,19 +340,20 @@ public class VibratorControllerTest {
@Test
public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
controller.registerVibratorStateListener(mVibratorStateListenerMock);
verify(mVibratorStateListenerMock).onVibrating(false);
- controller.on(10, 1);
+ controller.on(10, 1, 1);
verify(mVibratorStateListenerMock).onVibrating(true);
controller.unregisterVibratorStateListener(mVibratorStateListenerMock);
Mockito.clearInvocations(mVibratorStateListenerMock);
- controller.on(10, 1);
+ controller.on(10, 1, 1);
verifyNoMoreInteractions(mVibratorStateListenerMock);
}
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 3f3476716831..bd806b7f7f37 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -118,11 +118,11 @@ public final class FakeVibratorControllerProvider {
}
@Override
- public long on(long milliseconds, long vibrationId) {
+ public long on(long milliseconds, long vibrationId, long stepId) {
recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0, (int) milliseconds));
applyLatency(mOnLatency);
- scheduleListener(milliseconds, vibrationId);
+ scheduleListener(milliseconds, vibrationId, stepId);
return milliseconds;
}
@@ -139,7 +139,7 @@ public final class FakeVibratorControllerProvider {
}
@Override
- public long perform(long effect, long strength, long vibrationId) {
+ public long perform(long effect, long strength, long vibrationId, long stepId) {
if (mSupportedEffects == null
|| Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
return 0;
@@ -147,13 +147,13 @@ public final class FakeVibratorControllerProvider {
recordEffectSegment(vibrationId,
new PrebakedSegment((int) effect, false, (int) strength));
applyLatency(mOnLatency);
- scheduleListener(EFFECT_DURATION, vibrationId);
+ scheduleListener(EFFECT_DURATION, vibrationId, stepId);
return EFFECT_DURATION;
}
@Override
public long performVendorEffect(Parcel vendorData, long strength, float scale,
- float adaptiveScale, long vibrationId) {
+ float adaptiveScale, long vibrationId, long stepId) {
if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) {
return 0;
}
@@ -161,13 +161,13 @@ public final class FakeVibratorControllerProvider {
recordVendorEffect(vibrationId,
new VibrationEffect.VendorEffect(bundle, (int) strength, scale, adaptiveScale));
applyLatency(mOnLatency);
- scheduleListener(mVendorEffectDuration, vibrationId);
+ scheduleListener(mVendorEffectDuration, vibrationId, stepId);
// HAL has unknown duration for vendor effects.
return Long.MAX_VALUE;
}
@Override
- public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+ public long compose(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
if (mSupportedPrimitives == null) {
return 0;
}
@@ -182,12 +182,13 @@ public final class FakeVibratorControllerProvider {
recordEffectSegment(vibrationId, primitive);
}
applyLatency(mOnLatency);
- scheduleListener(duration, vibrationId);
+ scheduleListener(duration, vibrationId, stepId);
return duration;
}
@Override
- public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
+ public long composePwle(RampSegment[] primitives, int braking, long vibrationId,
+ long stepId) {
long duration = 0;
for (RampSegment primitive : primitives) {
duration += primitive.getDuration();
@@ -195,19 +196,19 @@ public final class FakeVibratorControllerProvider {
}
recordBraking(vibrationId, braking);
applyLatency(mOnLatency);
- scheduleListener(duration, vibrationId);
+ scheduleListener(duration, vibrationId, stepId);
return duration;
}
@Override
- public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
+ public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
long duration = 0;
for (PwlePoint pwlePoint: pwlePoints) {
duration += pwlePoint.getTimeMillis();
recordEffectPwlePoint(vibrationId, pwlePoint);
}
applyLatency(mOnLatency);
- scheduleListener(duration, vibrationId);
+ scheduleListener(duration, vibrationId, stepId);
return duration;
}
@@ -263,8 +264,8 @@ public final class FakeVibratorControllerProvider {
}
}
- private void scheduleListener(long vibrationDuration, long vibrationId) {
- mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId),
+ private void scheduleListener(long vibrationDuration, long vibrationId, long stepId) {
+ mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId, stepId),
vibrationDuration + mCompletionCallbackDelay);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 8e2cea7a8c78..6d8a48799112 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -244,9 +244,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
{"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, KeyEvent.KEYCODE_L,
META_ON},
- {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N,
- META_ON | CTRL_ON},
{"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 32a3b7f2c9cc..22c86eb3a9b8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -272,6 +272,19 @@ public class PhoneWindowManagerTests {
}
@Test
+ public void powerPress_withoutDreamManagerInternal_doesNotCrash() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ mDreamManagerInternal = null;
+ initPhoneWindowManager();
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // verify no crash
+ }
+
+ @Test
public void powerPress_hubOrDreamOrSleep_hubAvailableLocks() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
mContext.getTestablePermissions().setPermission(android.Manifest.permission.DEVICE_POWER,
@@ -352,5 +365,10 @@ public class PhoneWindowManagerTests {
WindowWakeUpPolicy getWindowWakeUpPolicy() {
return mock(WindowWakeUpPolicy.class);
}
+
+ @Override
+ DreamManagerInternal getDreamManagerInternal() {
+ return mDreamManagerInternal;
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 6923deec1ebb..301a7544227f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -56,8 +56,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
@@ -921,12 +919,6 @@ public class ActivityRecordTests extends WindowTestsBase {
// animation and AR#takeSceneTransitionInfo also clear the AR#mPendingOptions
assertNull(activity.takeSceneTransitionInfo());
assertNull(activity.getOptions());
-
- final AppTransition appTransition = activity.mDisplayContent.mAppTransition;
- spyOn(appTransition);
- activity.applyOptionsAnimation();
-
- verify(appTransition).overridePendingAppTransitionRemote(any());
}
@Test
@@ -1009,6 +1001,8 @@ public class ActivityRecordTests extends WindowTestsBase {
assertTrue(style.disablePreview());
assertTrue(style.optOutEdgeToEdge());
assertEquals(1 /* icon_preferred */, style.mSplashScreenBehavior);
+ assertEquals(style.noDisplay(), mAtm.mInternal.isNoDisplay(activity.packageName,
+ activity.info.theme, activity.mUserId));
}
/**
@@ -1188,7 +1182,6 @@ public class ActivityRecordTests extends WindowTestsBase {
FINISH_RESULT_REQUESTED, activity.finishIfPossible("test", false /* oomAdj */));
assertEquals(PAUSING, activity.getState());
verify(activity).setVisibility(eq(false));
- verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
}
/**
@@ -1235,7 +1228,6 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishIfPossible("test", false /* oomAdj */);
verify(activity).setVisibility(eq(false));
- verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
verify(activity.mDisplayContent, never()).executeAppTransition();
}
@@ -1252,7 +1244,6 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishIfPossible("test", false /* oomAdj */);
verify(activity, atLeast(1)).setVisibility(eq(false));
- verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
verify(activity.mDisplayContent).executeAppTransition();
}
@@ -1273,7 +1264,6 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishIfPossible("test", false /* oomAdj */);
- verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
assertFalse(activity.inTransition());
// finishIfPossible -> completeFinishing -> addToFinishingAndWaitForIdle
@@ -2655,10 +2645,6 @@ public class ActivityRecordTests extends WindowTestsBase {
@Presubmit
public void testGetOrientation() {
mDisplayContent.setIgnoreOrientationRequest(false);
- // ActivityBuilder will resume top activities and cause the activity been added into
- // opening apps list. Since this test is focus on the effect of visible on getting
- // orientation, we skip app transition to avoid interference.
- doNothing().when(mDisplayContent).prepareAppTransition(anyInt());
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.setVisible(true);
@@ -2923,7 +2909,6 @@ public class ActivityRecordTests extends WindowTestsBase {
sources.add(activity2);
doReturn(true).when(activity2).okToAnimate();
doReturn(true).when(activity2).isAnimating();
- assertTrue(activity2.applyAnimation(null, TRANSIT_OLD_ACTIVITY_OPEN, true, false, sources));
}
@Test
public void testTrackingStartingWindowThroughTrampoline() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d5b9751b0f51..9c483846217b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -63,9 +63,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -1742,8 +1739,6 @@ public class DisplayContentTests extends WindowTestsBase {
public void testFixedRotationWithPip() {
final DisplayContent displayContent = mDefaultDisplay;
displayContent.setIgnoreOrientationRequest(false);
- // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
- doNothing().when(displayContent).prepareAppTransition(anyInt());
// Make resume-top really update the activity state.
setBooted(mAtm);
clearInvocations(mWm);
@@ -1826,7 +1821,7 @@ public class DisplayContentTests extends WindowTestsBase {
.setTask(nonTopVisible.getTask()).setVisible(false)
.setActivityTheme(android.R.style.Theme_Translucent).build();
final TestTransitionPlayer player = registerTestTransitionPlayer();
- mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0, null);
translucentTop.setVisibility(true);
mDisplayContent.updateOrientation();
assertEquals("Non-top visible activity must be portrait",
@@ -2373,33 +2368,6 @@ public class DisplayContentTests extends WindowTestsBase {
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
- public void testShowImeScreenshot() {
- final Task rootTask = createTask(mDisplayContent);
- final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
- final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
- final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
- activity).build();
- task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
- doReturn(true).when(task).okToAnimate();
- ArrayList<WindowContainer> sources = new ArrayList<>();
- sources.add(activity);
-
- mDisplayContent.setImeLayeringTarget(win);
- spyOn(mDisplayContent);
-
- // Expecting the IME screenshot only be attached when performing task closing transition.
- task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
- false /* isVoiceInteraction */, sources);
- verify(mDisplayContent).showImeScreenshot();
-
- clearInvocations(mDisplayContent);
- activity.applyAnimation(null, TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, false /* enter */,
- false /* isVoiceInteraction */, sources);
- verify(mDisplayContent, never()).showImeScreenshot();
- }
-
- @SetupWindows(addWindows = W_INPUT_METHOD)
- @Test
public void testShowImeScreenshot_removeCurSnapshotBeforeCreateNext() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -2427,32 +2395,6 @@ public class DisplayContentTests extends WindowTestsBase {
@UseTestDisplay(addWindows = {W_INPUT_METHOD})
@Test
- public void testRemoveImeScreenshot_whenTargetSurfaceWasInvisible() {
- final Task rootTask = createTask(mDisplayContent);
- final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
- final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
- final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
- activity).build();
- win.onSurfaceShownChanged(true);
- makeWindowVisible(win, mDisplayContent.mInputMethodWindow);
- task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
- doReturn(true).when(task).okToAnimate();
- ArrayList<WindowContainer> sources = new ArrayList<>();
- sources.add(activity);
-
- mDisplayContent.setImeLayeringTarget(win);
- mDisplayContent.setImeInputTarget(win);
- mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
- task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
- false /* isVoiceInteraction */, sources);
- assertNotNull(mDisplayContent.mImeScreenshot);
-
- win.onSurfaceShownChanged(false);
- assertNull(mDisplayContent.mImeScreenshot);
- }
-
- @UseTestDisplay(addWindows = {W_INPUT_METHOD})
- @Test
public void testRemoveImeScreenshot_whenWindowRemoveImmediately() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
index db90c28ec7df..2d4101e40615 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -17,17 +17,21 @@
package com.android.server.wm;
import static android.view.Display.FLAG_PRESENTATION;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
+import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.UserHandle;
-import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
@@ -41,6 +45,7 @@ import android.view.WindowManagerGlobal;
import androidx.test.filters.SmallTest;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,35 +58,100 @@ import org.junit.runner.RunWith;
@RunWith(WindowTestRunner.class)
public class PresentationControllerTests extends WindowTestsBase {
+ TestTransitionPlayer mPlayer;
+
+ @Before
+ public void setUp() {
+ mPlayer = registerTestTransitionPlayer();
+ }
+
@EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
@Test
- public void testPresentationHidesActivitiesBehind() {
- final DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.copyFrom(mDisplayInfo);
- displayInfo.flags = FLAG_PRESENTATION;
- final DisplayContent dc = createNewDisplay(displayInfo);
- final int displayId = dc.getDisplayId();
- doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+ public void testPresentationShowAndHide() {
+ final DisplayContent dc = createPresentationDisplay();
final ActivityRecord activity = createActivityRecord(createTask(dc));
assertTrue(activity.isVisible());
- doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
- final int uid = 100000; // uid for non-system user
+ // Add a presentation window, which requests the activity to stop.
+ final WindowState window = addPresentationWindow(100000, dc.mDisplayId);
+ assertFalse(activity.isVisibleRequested());
+ assertTrue(activity.isVisible());
+ final Transition addTransition = window.mTransitionController.getCollectingTransition();
+ assertEquals(TRANSIT_OPEN, addTransition.mType);
+ assertTrue(addTransition.isInTransition(window));
+ assertTrue(addTransition.isInTransition(activity));
+
+ // Completing the transition makes the activity invisible.
+ completeTransition(addTransition, /*abortSync=*/ true);
+ assertFalse(activity.isVisible());
+
+ // Remove a Presentation window, which requests the activity to be resumed back.
+ window.removeIfPossible();
+ final Transition removeTransition = window.mTransitionController.getCollectingTransition();
+ assertEquals(TRANSIT_CLOSE, removeTransition.mType);
+ assertTrue(removeTransition.isInTransition(window));
+ assertTrue(removeTransition.isInTransition(activity));
+ assertTrue(activity.isVisibleRequested());
+ assertFalse(activity.isVisible());
+
+ // Completing the transition makes the activity visible.
+ completeTransition(removeTransition, /*abortSync=*/ false);
+ assertTrue(activity.isVisible());
+ }
+
+ @DisableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
+ @Test
+ public void testPresentationShowAndHide_flagDisabled() {
+ final DisplayContent dc = createPresentationDisplay();
+ final ActivityRecord activity = createActivityRecord(createTask(dc));
+ assertTrue(activity.isVisible());
+
+ final WindowState window = addPresentationWindow(100000, dc.mDisplayId);
+ assertFalse(window.mTransitionController.isCollecting());
+ assertTrue(activity.isVisibleRequested());
+ assertTrue(activity.isVisible());
+
+ window.removeIfPossible();
+ assertFalse(window.mTransitionController.isCollecting());
+ assertTrue(activity.isVisibleRequested());
+ assertTrue(activity.isVisible());
+ assertFalse(window.isAttached());
+ }
+
+ private WindowState addPresentationWindow(int uid, int displayId) {
final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
final int userId = UserHandle.getUserId(uid);
- doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
+ doReturn(true).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_PRESENTATION);
-
final IWindow clientWindow = new TestIWindow();
- final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
+ final int res = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
new InsetsSourceControl.Array(), new Rect(), new float[1]);
- assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
- assertFalse(activity.isVisible());
-
+ assertTrue(res >= WindowManagerGlobal.ADD_OKAY);
final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
- window.removeImmediately();
- assertTrue(activity.isVisible());
+ window.mHasSurface = true;
+ return window;
+ }
+
+ private DisplayContent createPresentationDisplay() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.flags = FLAG_PRESENTATION;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ final int displayId = dc.getDisplayId();
+ doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+ return dc;
+ }
+
+ private void completeTransition(@NonNull Transition transition, boolean abortSync) {
+ final ActionChain chain = ActionChain.testFinish(transition);
+ if (abortSync) {
+ // Forcefully finishing the active sync for testing purpose.
+ mWm.mSyncEngine.abort(transition.getSyncId());
+ } else {
+ transition.onTransactionReady(transition.getSyncId(), mTransaction);
+ }
+ transition.finishTransition(chain);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 59335d3bb135..3776b03695d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -98,6 +98,7 @@ import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.testutils.StubTransaction;
import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.window.flags.Flags;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -263,7 +264,7 @@ public class SystemServicesTestRule implements TestRule {
final DisplayManagerGlobal dmg = DisplayManagerGlobal.getInstance();
spyOn(dmg);
doNothing().when(dmg).registerDisplayListener(
- any(), any(Executor.class), anyLong(), anyString());
+ any(), any(Executor.class), anyLong(), anyString(), anyBoolean());
doNothing().when(dmg).registerTopologyListener(any(Executor.class), any(), anyString());
}
@@ -657,6 +658,13 @@ public class SystemServicesTestRule implements TestRule {
AppWarnings appWarnings = getAppWarningsLocked();
spyOn(appWarnings);
doNothing().when(appWarnings).onStartActivity(any());
+
+ if (Flags.trackSystemUiContextBeforeWms()) {
+ final Context uiContext = getUiContext();
+ spyOn(uiContext);
+ doNothing().when(uiContext).registerComponentCallbacks(any());
+ doNothing().when(uiContext).unregisterComponentCallbacks(any());
+ }
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index ccce57a81e41..b1525cf00dc6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -30,6 +30,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -39,6 +40,7 @@ import android.view.DisplayCutout;
import android.view.DisplayInfo;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
+import com.android.window.flags.Flags;
class TestDisplayContent extends DisplayContent {
@@ -79,6 +81,13 @@ class TestDisplayContent extends DisplayContent {
WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+ if (Flags.trackSystemUiContextBeforeWms()) {
+ final Context uiContext = getDisplayUiContext();
+ spyOn(uiContext);
+ doNothing().when(uiContext).registerComponentCallbacks(any());
+ doNothing().when(uiContext).unregisterComponentCallbacks(any());
+ }
+
// For devices that set the sysprop ro.bootanim.set_orientation_<display_id>
// See DisplayRotation#readDefaultDisplayRotation for context.
// Without that, meaning of height and width in context of the tests can be swapped if
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 2ee34d3a4b36..77ad7f7bac7f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -683,7 +683,7 @@ public class TransitionTests extends WindowTestsBase {
app.onStartingWindowDrawn();
// The task appeared event should be deferred until transition ready.
assertFalse(task.taskAppearedReady());
- testPlayer.onTransactionReady(app.getSyncTransaction());
+ testPlayer.onTransactionReady();
assertTrue(task.taskAppearedReady());
assertTrue(playerProc.isRunningRemoteTransition());
assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
@@ -1162,7 +1162,8 @@ public class TransitionTests extends WindowTestsBase {
screenDecor.updateSurfacePosition(mMockT);
assertEquals(prevPos, screenDecor.mLastSurfacePosition);
- final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction startTransaction = mTransaction;
+ clearInvocations(startTransaction);
final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
onRotationTransactionReady(player, startTransaction);
@@ -1213,7 +1214,8 @@ public class TransitionTests extends WindowTestsBase {
assertFalse(statusBar.mToken.inTransition());
assertTrue(app.getTask().inTransition());
- final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction startTransaction = mTransaction;
+ clearInvocations(startTransaction);
final SurfaceControl leash = statusBar.mToken.getAnimationLeash();
doReturn(true).when(leash).isValid();
final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
@@ -1287,7 +1289,8 @@ public class TransitionTests extends WindowTestsBase {
// Avoid DeviceStateController disturbing the test by triggering another rotation change.
doReturn(false).when(mDisplayContent).updateRotationUnchecked();
- onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted();
+ clearInvocations(mTransaction);
+ onRotationTransactionReady(player, mTransaction).onTransactionCommitted();
assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange(
mDisplayContent.mRemoteToken.toWindowContainerToken()).getRotationAnimation());
spyOn(navBarInsetsProvider);
@@ -1350,7 +1353,7 @@ public class TransitionTests extends WindowTestsBase {
mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
doReturn(true).when(home).hasFixedRotationTransform(any());
player.startTransition();
- player.onTransactionReady(mDisplayContent.getSyncTransaction());
+ player.onTransactionReady();
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
final RemoteDisplayChangeController displayChangeController = mDisplayContent
@@ -3071,8 +3074,11 @@ public class TransitionTests extends WindowTestsBase {
TestTransitionPlayer player, SurfaceControl.Transaction startTransaction) {
final ArgumentCaptor<SurfaceControl.TransactionCommittedListener> listenerCaptor =
ArgumentCaptor.forClass(SurfaceControl.TransactionCommittedListener.class);
- player.onTransactionReady(startTransaction);
- verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
+ player.onTransactionReady();
+ // The startTransaction is from mWm.mTransactionFactory.get() in SyncGroup#finishNow.
+ // 2 times are from SyncGroup#finishNow and AsyncRotationController#setupStartTransaction.
+ verify(startTransaction, times(2)).addTransactionCommittedListener(
+ any(), listenerCaptor.capture());
return listenerCaptor.getValue();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index cee98fb1b34c..4f310de1e48b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -30,8 +30,6 @@ import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemOverlays;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -1121,7 +1119,6 @@ public class WindowContainerTests extends WindowTestsBase {
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
activity).build();
- task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
spyOn(win);
doReturn(true).when(task).okToAnimate();
ArrayList<WindowContainer> sources = new ArrayList<>();
@@ -1130,8 +1127,6 @@ public class WindowContainerTests extends WindowTestsBase {
// Simulate the task applying the exit transition, verify the main window of the task
// will be set the frozen insets state before the animation starts
activity.setVisibility(false);
- task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
- false /* isVoiceInteraction */, sources);
verify(win).freezeInsetsState();
// Simulate the task transition finished.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 7f9e591ca5e3..c6416850c464 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -861,11 +861,9 @@ public class WindowTestsBase extends SystemServiceTestsBase {
/** Creates a {@link DisplayContent} and adds it to the system. */
private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy,
@Nullable SettingsEntry overrideSettings) {
- final DisplayContent display =
- new TestDisplayContent.Builder(mAtm, info)
- .setOverrideSettings(overrideSettings)
- .build();
- final DisplayContent dc = display.mDisplayContent;
+ final DisplayContent dc = new TestDisplayContent.Builder(mAtm, info)
+ .setOverrideSettings(overrideSettings)
+ .build();
// this display can show IME.
dc.mWmService.mDisplayWindowSettings.setDisplayImePolicy(dc, imePolicy);
return dc;
@@ -2161,13 +2159,14 @@ public class WindowTestsBase extends SystemServiceTestsBase {
mOrganizer.startTransition(mLastTransit.getToken(), null);
}
- void onTransactionReady(SurfaceControl.Transaction t) {
- mLastTransit.onTransactionReady(mLastTransit.getSyncId(), t);
+ void onTransactionReady() {
+ // SyncGroup#finishNow -> Transition#onTransactionReady.
+ mController.mSyncEngine.abort(mLastTransit.getSyncId());
}
void start() {
startTransition();
- onTransactionReady(mock(SurfaceControl.Transaction.class));
+ onTransactionReady();
}
public void finish() {
diff --git a/startop/OWNERS b/startop/OWNERS
index 11d5ad0f000a..3414a7469ac2 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -1,2 +1 @@
include platform/art:/OWNERS
-keunyoung@google.com
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt
new file mode 100644
index 000000000000..c82ce8a4cb1d
--- /dev/null
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.wm.flicker.service.transitions.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.transitions.scenarios.LaunchTask
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class LaunchTaskPortrait : LaunchTask(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["TASK_TRANSITION_SCENARIO", "OPEN_NEW_TASK_APP_SCENARIO"])
+ @Test
+ override fun openNewTask() = super.openNewTask()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(TASK_TRANSITION_SCENARIO)
+ .use(OPEN_NEW_TASK_APP_SCENARIO)
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt
new file mode 100644
index 000000000000..147477d728be
--- /dev/null
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.wm.flicker.service.transitions.scenarios
+
+import android.app.Instrumentation
+import android.tools.Rotation
+import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.assertors.assertions.AppWindowCoversFullScreenAtStart
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.flicker.assertors.assertions.BackgroundShowsInTransition
+import android.tools.flicker.assertors.assertions.LayerBecomesInvisible
+import android.tools.flicker.assertors.assertions.LayerBecomesVisible
+import android.tools.flicker.assertors.assertions.LayerIsNeverVisible
+import android.tools.flicker.assertors.assertions.AppWindowIsNeverVisible
+import android.tools.flicker.config.AssertionTemplates
+import android.tools.flicker.config.FlickerConfigEntry
+import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.appclose.Components.CLOSING_APPS
+import android.tools.flicker.config.appclose.Components.CLOSING_CHANGES
+import android.tools.flicker.config.applaunch.Components.OPENING_CHANGES
+import android.tools.flicker.config.common.Components.LAUNCHER
+import android.tools.flicker.config.common.Components.WALLPAPER
+import android.tools.flicker.extractors.TaggedCujTransitionMatcher
+import android.tools.flicker.extractors.TaggedScenarioExtractorBuilder
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.events.CujType
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+
+/**
+ * This tests performs a transition between tasks
+ */
+abstract class LaunchTask(val rotation: Rotation = Rotation.ROTATION_0) {
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val tapl = LauncherInstrumentation()
+
+ private val launchNewTaskApp = NewTasksAppHelper(instrumentation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ tapl.setExpectedRotation(rotation.value)
+ launchNewTaskApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun openNewTask() {
+ launchNewTaskApp.openNewTask(device, wmHelper)
+ }
+
+ @After
+ fun tearDown() {
+ launchNewTaskApp.exit(wmHelper)
+ }
+
+ companion object {
+ /**
+ * General task transition scenario that can be reused for any trace
+ */
+ val TASK_TRANSITION_SCENARIO =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("TASK_TRANSITION_SCENARIO"),
+ extractor = TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = true)
+ )
+ .build(),
+ assertions = listOf(
+ // Opening changes replace the closing ones
+ LayerBecomesInvisible(CLOSING_CHANGES),
+ AppWindowOnTopAtStart(CLOSING_CHANGES),
+ LayerBecomesVisible(OPENING_CHANGES),
+ AppWindowOnTopAtEnd(OPENING_CHANGES),
+
+ // There is a background color and it's covering the transition area
+ BackgroundShowsInTransition(CLOSING_CHANGES)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
+
+ /**
+ * Scenario that is making assertions that are valid for the new task app but that do not
+ * apply to other task transitions in general
+ */
+ val OPEN_NEW_TASK_APP_SCENARIO =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("OPEN_NEW_TASK_APP_SCENARIO"),
+ extractor = TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = true)
+ )
+ .build(),
+ assertions = AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ // Wallpaper and launcher never visible
+ LayerIsNeverVisible(WALLPAPER, mustExist = true),
+ LayerIsNeverVisible(LAUNCHER, mustExist = true),
+ AppWindowIsNeverVisible(LAUNCHER, mustExist = true),
+ // App window covers the display at start
+ AppWindowCoversFullScreenAtStart(CLOSING_APPS)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
+ }
+} \ No newline at end of file
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 1f0bd61b5c3f..168141bf6e7d 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -19,6 +19,9 @@ android_test {
"src/**/*.kt",
],
asset_dirs: ["assets"],
+ kotlincflags: [
+ "-Werror",
+ ],
platform_apis: true,
certificate: "platform",
static_libs: [
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 99c5bad7b2b9..c666fb7e05f1 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -371,18 +371,6 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "META + CTRL + N -> Open Notes",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_N
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES,
- intArrayOf(KeyEvent.KEYCODE_N),
- KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
"META + S -> Take Screenshot",
intArrayOf(
KeyEvent.KEYCODE_META_LEFT,
diff --git a/tests/SoundTriggerTestApp/OWNERS b/tests/SoundTriggerTestApp/OWNERS
index a0fcfc52704d..1e41886fe716 100644
--- a/tests/SoundTriggerTestApp/OWNERS
+++ b/tests/SoundTriggerTestApp/OWNERS
@@ -1,2 +1 @@
include /media/java/android/media/soundtrigger/OWNERS
-mdooley@google.com
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
index be0c7daebb57..e62a89b17927 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -19,6 +19,7 @@ package com.android.internal.protolog;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.ArgumentMatchers.endsWith;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.times;
@@ -157,13 +158,15 @@ public class ProtoLogCommandHandlerTest {
cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" });
- Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP");
+ Mockito.verify(mProtoLogConfigurationService)
+ .enableProtoLogToLogcat(Mockito.any(), eq("MY_GROUP"));
cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err,
new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
Mockito.verify(mProtoLogConfigurationService)
- .enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+ .enableProtoLogToLogcat(Mockito.any(),
+ eq("MY_GROUP"), eq("MY_OTHER_GROUP"));
}
@Test
@@ -173,13 +176,15 @@ public class ProtoLogCommandHandlerTest {
cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" });
- Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP");
+ Mockito.verify(mProtoLogConfigurationService)
+ .disableProtoLogToLogcat(Mockito.any(), eq("MY_GROUP"));
cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err,
new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
Mockito.verify(mProtoLogConfigurationService)
- .disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+ .disableProtoLogToLogcat(Mockito.any(),
+ eq("MY_GROUP"), eq("MY_OTHER_GROUP"));
}
@Test
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
index 3be725101252..1f3f91ebf557 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -62,6 +62,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.List;
/**
@@ -234,7 +235,7 @@ public class ProtoLogConfigurationServiceTest {
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
- service.enableProtoLogToLogcat(TEST_GROUP);
+ service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
Mockito.verify(mMockClient).toggleLogcat(eq(true),
@@ -251,7 +252,7 @@ public class ProtoLogConfigurationServiceTest {
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
- service.disableProtoLogToLogcat(TEST_GROUP);
+ service.disableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
Mockito.verify(mMockClient).toggleLogcat(eq(false),
@@ -269,7 +270,7 @@ public class ProtoLogConfigurationServiceTest {
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
- service.enableProtoLogToLogcat(OTHER_TEST_GROUP);
+ service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), OTHER_TEST_GROUP);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
Mockito.verify(mMockClient, never()).toggleLogcat(anyBoolean(), any());
@@ -280,7 +281,7 @@ public class ProtoLogConfigurationServiceTest {
final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
- service.enableProtoLogToLogcat(TEST_GROUP);
+ service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
final RegisterClientArgs args = new RegisterClientArgs();
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 1273826c8ef8..8cd89ce89e83 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -273,7 +273,7 @@ public class TestableLooper {
messages.add(message);
}
- // Repost all Messages back to the queuewith a new time.
+ // Repost all Messages back to the queue with a new time.
while (true) {
Message message = messages.poll();
if (message == null) {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index ff4d8ef2ec25..0a5cb1ff4956 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -2649,6 +2649,10 @@ int LinkCommand::Action(const std::vector<std::string>& args) {
".mpg", ".mpeg", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".wma", ".wmv",
".webm", ".mkv"});
+ if (options_.no_compress_fonts) {
+ options_.extensions_to_not_compress.insert({".ttf", ".otf", ".ttc"});
+ }
+
// Turn off auto versioning for static-libs.
if (context.GetPackageType() == PackageType::kStaticLib) {
options_.no_auto_version = true;
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 2f17853718ec..977978834fcd 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -78,6 +78,7 @@ struct LinkOptions {
bool use_sparse_encoding = false;
std::unordered_set<std::string> extensions_to_not_compress;
std::optional<std::regex> regex_to_not_compress;
+ bool no_compress_fonts = false;
FeatureFlagValues feature_flag_values;
// Static lib options.
@@ -300,6 +301,14 @@ class LinkCommand : public Command {
"use the '$' symbol for end of line. Uses a case-sensitive ECMAScript"
"regular expression grammar.",
&no_compress_regex);
+ AddOptionalSwitch("--no-compress-fonts",
+ "Do not compress files with common extensions for fonts.\n"
+ "This allows loading fonts directly from the APK, without needing to\n"
+ "decompress them first. Loading fonts will be faster and use less memory.\n"
+ "The downside is that the APK will be larger.\n"
+ "Passing this flag is functionally equivalent to passing the following flags:\n"
+ "-0 .ttf -0 .otf -0 .ttc",
+ &options_.no_compress_fonts);
AddOptionalSwitch("--warn-manifest-validation",
"Treat manifest validation errors as warnings.",
&options_.manifest_fixer_options.warn_validation);
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index a2dc8f8ce0fd..41f8e250efd7 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -98,6 +98,7 @@ TEST_F(LinkTest, NoCompressAssets) {
WriteFile(GetTestPath("assets/test.txt"), content);
WriteFile(GetTestPath("assets/test.hello.txt"), content);
WriteFile(GetTestPath("assets/test.hello.xml"), content);
+ WriteFile(GetTestPath("assets/fonts/myfont.ttf"), content);
const std::string out_apk = GetTestPath("out.apk");
std::vector<std::string> link_args = {
@@ -136,6 +137,10 @@ TEST_F(LinkTest, NoCompressAssets) {
file = zip->FindFile("assets/test.hello.xml");
ASSERT_THAT(file, Ne(nullptr));
EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/fonts/myfont.ttf");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_TRUE(file->WasCompressed());
}
TEST_F(LinkTest, NoCompressResources) {
@@ -182,6 +187,42 @@ TEST_F(LinkTest, NoCompressResources) {
EXPECT_FALSE(file->WasCompressed());
}
+TEST_F(LinkTest, NoCompressFonts) {
+ StdErrDiagnostics diag;
+ std::string content(500, 'a');
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test.txt"), content, compiled_files_dir, &diag));
+ WriteFile(GetTestPath("assets/fonts/myfont1.ttf"), content);
+ WriteFile(GetTestPath("assets/fonts/myfont2.ttf"), content);
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(),
+ "-o", out_apk,
+ "--no-compress-fonts",
+ "-A", GetTestPath("assets")
+ };
+
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+ io::IFileCollection* zip = apk->GetFileCollection();
+ ASSERT_THAT(zip, Ne(nullptr));
+
+ auto file = zip->FindFile("res/raw/test.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_TRUE(file->WasCompressed());
+
+ file = zip->FindFile("assets/fonts/myfont1.ttf");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/fonts/myfont2.ttf");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+}
+
TEST_F(LinkTest, OverlayStyles) {
StdErrDiagnostics diag;
const std::string compiled_files_dir = GetTestPath("compiled");
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 5c3dfdcadfec..6bdbaaed9858 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -3,6 +3,8 @@
## Version 2.20
- Too many features, bug fixes, and improvements to list since the last minor version update in
2017. This README will be updated more frequently in the future.
+- Added a new flag `--no-compress-fonts`. This can significantly speed up loading fonts from APK
+ assets, at the cost of increasing the storage size of the APK.
## Version 2.19
- Added navigation resource type.