summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java55
-rw-r--r--core/api/current.txt2
-rw-r--r--core/api/system-current.txt6
-rw-r--r--core/api/test-current.txt4
-rw-r--r--core/java/android/accounts/AccountManager.java10
-rw-r--r--core/java/android/app/Activity.java6
-rw-r--r--core/java/android/app/Instrumentation.java42
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/app/NotificationManager.java170
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java18
-rw-r--r--core/java/android/app/appfunctions/AppFunctionService.java32
-rw-r--r--core/java/android/app/appfunctions/IAppFunctionService.aidl3
-rw-r--r--core/java/android/app/time/TimeZoneCapabilities.java61
-rw-r--r--core/java/android/app/time/TimeZoneConfiguration.java50
-rw-r--r--core/java/android/content/Intent.java56
-rw-r--r--core/java/android/content/pm/SigningInfo.aidl19
-rw-r--r--core/java/android/content/res/ApkAssets.java2
-rw-r--r--core/java/android/hardware/contexthub/HubEndpoint.java23
-rw-r--r--core/java/android/hardware/contexthub/IContextHubEndpoint.aidl6
-rw-r--r--core/java/android/hardware/input/InputGestureData.java121
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java4
-rw-r--r--core/java/android/os/PowerManager.java1
-rw-r--r--core/java/android/provider/Settings.java20
-rw-r--r--core/java/android/text/Layout.java2
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java9
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java16
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig6
-rw-r--r--core/java/com/android/internal/accessibility/util/AccessibilityUtils.java21
-rw-r--r--core/java/com/android/internal/notification/SystemNotificationChannels.java15
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressBar.java605
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressDrawable.java372
-rw-r--r--core/proto/android/providers/settings/secure.proto2
-rw-r--r--core/res/res/drawable/notification_progress.xml1
-rw-r--r--core/res/res/layout/notification_2025_conversation_header.xml24
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml4
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml4
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml4
-rw-r--r--core/res/res/layout/notification_2025_template_header.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_heads_up_base.xml2
-rw-r--r--core/res/res/layout/notification_2025_top_line_views.xml159
-rw-r--r--core/res/res/values-watch/config.xml11
-rw-r--r--core/res/res/values/attrs.xml22
-rw-r--r--core/res/res/values/config.xml12
-rw-r--r--core/res/res/values/dimens.xml19
-rw-r--r--core/res/res/values/strings.xml26
-rw-r--r--core/res/res/values/symbols.xml16
-rw-r--r--core/tests/coretests/src/android/app/NotificationManagerTest.java213
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java77
-rw-r--r--core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java5
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java650
-rw-r--r--core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java44
-rw-r--r--core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java2
-rw-r--r--libs/WindowManager/Shell/Android.bp37
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt229
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt534
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java51
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java23
-rw-r--r--libs/hwui/jni/Typeface.cpp1
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.h1
-rw-r--r--media/TEST_MAPPING5
-rw-r--r--packages/CompanionDeviceManager/res/layout/activity_confirmation.xml4
-rw-r--r--packages/SettingsLib/DataStore/OWNERS1
-rw-r--r--packages/SettingsLib/Graph/OWNERS1
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt1
-rw-r--r--packages/SettingsLib/Ipc/OWNERS1
-rw-r--r--packages/SettingsLib/Metadata/OWNERS1
-rw-r--r--packages/SettingsLib/OWNERS_catalyst9
-rw-r--r--packages/SettingsLib/Preference/OWNERS1
-rw-r--r--packages/SettingsLib/Service/OWNERS1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java19
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java51
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java72
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt34
-rw-r--r--packages/SystemUI/compose/core/tests/AndroidManifest.xml4
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt30
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt76
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt12
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt46
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt24
-rw-r--r--packages/SystemUI/proguard_common.flags8
-rw-r--r--packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml199
-rw-r--r--packages/SystemUI/res/drawable/ic_media_pause_button.xml135
-rw-r--r--packages/SystemUI/res/drawable/ic_media_pause_button_container.xml135
-rw-r--r--packages/SystemUI/res/drawable/ic_media_play_button.xml124
-rw-r--r--packages/SystemUI/res/drawable/ic_media_play_button_container.xml135
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider.xml5
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_button.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/res/values/strings.xml10
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_constraint_set.xml5
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/model/NotifStats.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt61
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt2
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java20
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AutoclickController.java70
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java17
-rw-r--r--services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java356
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java32
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java13
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java4
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java18
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java44
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java15
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayPolicy.java42
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java54
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/flags/services.aconfig8
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java8
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java30
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java4
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java121
-rw-r--r--services/core/java/com/android/server/media/TEST_MAPPING5
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java1
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java66
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java97
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/pm/SharedLibrariesImpl.java2
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java4
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java20
-rw-r--r--services/core/java/com/android/server/policy/TalkbackShortcutController.java32
-rw-r--r--services/core/java/com/android/server/policy/VoiceAccessShortcutController.java62
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java8
-rw-r--r--services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java88
-rw-r--r--services/core/java/com/android/server/timedetector/ServerFlags.java31
-rw-r--r--services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java3
-rw-r--r--services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java104
-rw-r--r--services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java85
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneChangeListener.java106
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java4
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java22
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java52
-rw-r--r--services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java8
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java16
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java16
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOverrides.java18
-rw-r--r--services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java2
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java4
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java2
-rw-r--r--services/core/java/com/android/server/wm/Transition.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp38
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java55
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java28
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java187
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java2
-rw-r--r--services/tests/timetests/src/com/android/server/timezonedetector/ConfigInternalForTests.java108
-rw-r--r--services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java2
-rw-r--r--services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java143
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java257
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/BasicToPwleSegmentAdapterTest.java158
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java36
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java107
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java29
-rw-r--r--telecomm/java/com/android/internal/telecom/ITelecomService.aidl6
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java18
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt18
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt3
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt2
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt14
288 files changed, 7637 insertions, 2110 deletions
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
index 2d2cf1c80e1e..b04d08f6795f 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
@@ -34,11 +34,20 @@ import android.view.WindowManagerGlobal;
import org.junit.Rule;
import org.junit.Test;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
/** Measure the performance of warm launch activity in the same task. */
public class InTaskTransitionTest extends WindowManagerPerfTestBase
implements RemoteCallback.OnResultListener {
private static final long TIMEOUT_MS = 5000;
+ private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@@ -62,6 +71,7 @@ public class InTaskTransitionTest extends WindowManagerPerfTestBase
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
long measuredTimeNs = 0;
+ long firstStartTime = 0;
boolean readerStarted = false;
while (state.keepRunning(measuredTimeNs)) {
@@ -70,6 +80,10 @@ public class InTaskTransitionTest extends WindowManagerPerfTestBase
readerStarted = true;
}
final long startTime = SystemClock.elapsedRealtimeNanos();
+ if (readerStarted && firstStartTime == 0) {
+ firstStartTime = startTime;
+ executeShellCommand("log -t " + LOG_SEPARATOR + " " + firstStartTime);
+ }
activity.startActivity(next);
synchronized (mMetricsReader) {
try {
@@ -89,6 +103,7 @@ public class InTaskTransitionTest extends WindowManagerPerfTestBase
state.addExtraResult("windowsDrawnDelayMs", metrics.mWindowsDrawnDelayMs);
}
}
+ addExtraTransitionInfo(firstStartTime, state);
}
@Override
@@ -99,6 +114,46 @@ public class InTaskTransitionTest extends WindowManagerPerfTestBase
}
}
+ private void addExtraTransitionInfo(long startTime, ManualBenchmarkState state) {
+ final ProcessBuilder pb = new ProcessBuilder("sh");
+ final String startLine = String.valueOf(startTime);
+ final String commitTimeStr = " commit=";
+ boolean foundStartLine = false;
+ try {
+ final Process process = pb.start();
+ final InputStream in = process.getInputStream();
+ final PrintWriter out = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(process.getOutputStream())), true /* autoFlush */);
+ out.println("logcat -v brief -d *:S WindowManager:V " + LOG_SEPARATOR + ":I"
+ + " | grep -e 'Finish Transition' -e " + LOG_SEPARATOR);
+ out.println("exit");
+
+ String line;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
+ while ((line = reader.readLine()) != null) {
+ if (!foundStartLine) {
+ if (line.contains(startLine)) {
+ foundStartLine = true;
+ }
+ continue;
+ }
+ final int strPos = line.indexOf(commitTimeStr);
+ if (strPos < 0) {
+ continue;
+ }
+ final int endPos = line.indexOf("ms", strPos);
+ if (endPos > strPos) {
+ final int commitDelayMs = Math.round(Float.parseFloat(
+ line.substring(strPos + commitTimeStr.length(), endPos)));
+ state.addExtraResult("commitDelayMs", commitDelayMs);
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/** The test activity runs on a different process to trigger metrics logs. */
public static class TestActivity extends Activity implements Runnable {
static final String CALLBACK = "callback";
diff --git a/core/api/current.txt b/core/api/current.txt
index c4109392d6bd..6707c15de682 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8905,7 +8905,7 @@ package android.app.appfunctions {
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.content.pm.SigningInfo, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0e833bdba1e1..93f311969c1e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -581,7 +581,7 @@ package android.accessibilityservice {
package android.accounts {
public class AccountManager {
- method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.COPY_ACCOUNTS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public android.accounts.AccountManagerFuture<java.lang.Boolean> copyAccountToUser(@NonNull android.accounts.Account, @NonNull android.os.UserHandle, @NonNull android.os.UserHandle, @Nullable android.accounts.AccountManagerCallback<java.lang.Boolean>, @Nullable android.os.Handler);
+ method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.COPY_ACCOUNTS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public android.accounts.AccountManagerFuture<java.lang.Boolean> copyAccountToUser(@NonNull android.accounts.Account, @NonNull android.os.UserHandle, @NonNull android.os.UserHandle, @Nullable android.os.Handler, @Nullable android.accounts.AccountManagerCallback<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.accounts.AccountManagerFuture<android.os.Bundle> finishSessionAsUser(android.os.Bundle, android.app.Activity, android.os.UserHandle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
}
@@ -18570,13 +18570,13 @@ package android.telephony.satellite {
@FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int[] getSatelliteDisallowedReasons();
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCommunicationAccessStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCommunicationAccessStateCallback);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a988acf1f4a9..a352d9d2ea06 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3415,6 +3415,10 @@ package android.telecom {
method public void onBindClient(@Nullable android.content.Intent);
}
+ public class TelecomManager {
+ method @FlaggedApi("com.android.server.telecom.flags.voip_call_monitor_refactor") public boolean hasForegroundServiceDelegation(@Nullable android.telecom.PhoneAccountHandle);
+ }
+
}
package android.telephony {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 72450999993d..ddc1ae29f6df 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -30,7 +30,6 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.Size;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
@@ -2019,23 +2018,22 @@ public class AccountManager {
* @param account the account to copy
* @param fromUser the user to copy the account from
* @param toUser the target user
- * @param callback Callback to invoke when the request completes,
- * null for no callback
* @param handler {@link Handler} identifying the callback thread,
* null for the main thread
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
* @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it
* succeeded.
* @hide
*/
- @SuppressLint("SamShouldBeLast")
@NonNull
@SystemApi
@RequiresPermission(anyOf = {COPY_ACCOUNTS, INTERACT_ACROSS_USERS_FULL})
@FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
public AccountManagerFuture<Boolean> copyAccountToUser(
@NonNull final Account account, @NonNull final UserHandle fromUser,
- @NonNull final UserHandle toUser, @Nullable AccountManagerCallback<Boolean> callback,
- @Nullable Handler handler) {
+ @NonNull final UserHandle toUser, @Nullable Handler handler,
+ @Nullable AccountManagerCallback<Boolean> callback) {
if (account == null) throw new IllegalArgumentException("account is null");
if (toUser == null || fromUser == null) {
throw new IllegalArgumentException("fromUser and toUser cannot be null");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b198811416cd..4782205e3b21 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1027,9 +1027,6 @@ public class Activity extends ContextThemeWrapper
/** The autofill client controller. Always access via {@link #getAutofillClientController()}. */
private AutofillClientController mAutofillClientController;
- /** @hide */
- boolean mEnterAnimationComplete;
-
private boolean mIsInMultiWindowMode;
/** @hide */
boolean mIsInPictureInPictureMode;
@@ -2898,7 +2895,6 @@ public class Activity extends ContextThemeWrapper
mCalled = true;
getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations);
- mEnterAnimationComplete = false;
notifyVoiceInteractionManagerServiceActivityEvent(
VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP);
@@ -8594,8 +8590,6 @@ public class Activity extends ContextThemeWrapper
* @hide
*/
public void dispatchEnterAnimationComplete() {
- mEnterAnimationComplete = true;
- mInstrumentation.onEnterAnimationComplete();
onEnterAnimationComplete();
if (getWindow() != null && getWindow().getDecorView() != null) {
View decorView = getWindow().getDecorView();
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 7eacaac29d4b..b611acf79bc3 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -59,7 +59,6 @@ import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.Window;
import android.view.WindowManagerGlobal;
@@ -137,7 +136,6 @@ public class Instrumentation {
private PerformanceCollector mPerformanceCollector;
private Bundle mPerfMetrics = new Bundle();
private UiAutomation mUiAutomation;
- private final Object mAnimationCompleteLock = new Object();
@RavenwoodKeep
public Instrumentation() {
@@ -455,31 +453,6 @@ public class Instrumentation {
idler.waitForIdle();
}
- private void waitForEnterAnimationComplete(Activity activity) {
- synchronized (mAnimationCompleteLock) {
- long timeout = 5000;
- try {
- // We need to check that this specified Activity completed the animation, not just
- // any Activity. If it was another Activity, then decrease the timeout by how long
- // it's already waited and wait for the thread to wakeup again.
- while (timeout > 0 && !activity.mEnterAnimationComplete) {
- long startTime = System.currentTimeMillis();
- mAnimationCompleteLock.wait(timeout);
- long totalTime = System.currentTimeMillis() - startTime;
- timeout -= totalTime;
- }
- } catch (InterruptedException e) {
- }
- }
- }
-
- /** @hide */
- public void onEnterAnimationComplete() {
- synchronized (mAnimationCompleteLock) {
- mAnimationCompleteLock.notifyAll();
- }
- }
-
/**
* Execute a call on the application's main thread, blocking until it is
* complete. Useful for doing things that are not thread-safe, such as
@@ -640,13 +613,14 @@ public class Instrumentation {
activity = aw.activity;
}
- // Do not call this method within mSync, lest it could block the main thread.
- waitForEnterAnimationComplete(activity);
-
- // Apply an empty transaction to ensure SF has a chance to update before
- // the Activity is ready (b/138263890).
- try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
- t.apply(true);
+ // Typically, callers expect that the launched activity can receive input events after this
+ // method returns, so wait until a stable state, i.e. animation is finished and input info
+ // is updated.
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .syncInputTransactions(true /* waitForAnimations */);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
return activity;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 614e2aaf42e8..5176aee9051f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5856,7 +5856,9 @@ public class Notification implements Parcelable
return null;
}
final int size = mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_badge_size);
+ Flags.notificationsRedesignTemplates()
+ ? R.dimen.notification_2025_badge_size
+ : R.dimen.notification_badge_size);
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
badge.setBounds(0, 0, size, size);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 08bd854525ec..aede8aa70ede 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.service.notification.Flags.notificationClassification;
@@ -50,6 +51,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -71,6 +73,8 @@ import android.util.LruCache;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.InstantSource;
@@ -1202,12 +1206,20 @@ public class NotificationManager {
* package (see {@link Context#createPackageContext(String, int)}).</p>
*/
public NotificationChannel getNotificationChannel(String channelId) {
- INotificationManager service = service();
- try {
- return service.getNotificationChannel(mContext.getOpPackageName(),
- mContext.getUserId(), mContext.getPackageName(), channelId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Flags.nmBinderPerfCacheChannels()) {
+ return getChannelFromList(channelId,
+ mNotificationChannelListCache.query(new NotificationChannelQuery(
+ mContext.getOpPackageName(),
+ mContext.getPackageName(),
+ mContext.getUserId())));
+ } else {
+ INotificationManager service = service();
+ try {
+ return service.getNotificationChannel(mContext.getOpPackageName(),
+ mContext.getUserId(), mContext.getPackageName(), channelId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -1222,13 +1234,21 @@ public class NotificationManager {
*/
public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
@NonNull String conversationId) {
- INotificationManager service = service();
- try {
- return service.getConversationNotificationChannel(mContext.getOpPackageName(),
- mContext.getUserId(), mContext.getPackageName(), channelId, true,
- conversationId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Flags.nmBinderPerfCacheChannels()) {
+ return getConversationChannelFromList(channelId, conversationId,
+ mNotificationChannelListCache.query(new NotificationChannelQuery(
+ mContext.getOpPackageName(),
+ mContext.getPackageName(),
+ mContext.getUserId())));
+ } else {
+ INotificationManager service = service();
+ try {
+ return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+ mContext.getUserId(), mContext.getPackageName(), channelId, true,
+ conversationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -1241,15 +1261,62 @@ public class NotificationManager {
* {@link Context#createPackageContext(String, int)}).</p>
*/
public List<NotificationChannel> getNotificationChannels() {
- INotificationManager service = service();
- try {
- return service.getNotificationChannels(mContext.getOpPackageName(),
- mContext.getPackageName(), mContext.getUserId()).getList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Flags.nmBinderPerfCacheChannels()) {
+ return mNotificationChannelListCache.query(new NotificationChannelQuery(
+ mContext.getOpPackageName(),
+ mContext.getPackageName(),
+ mContext.getUserId()));
+ } else {
+ INotificationManager service = service();
+ try {
+ return service.getNotificationChannels(mContext.getOpPackageName(),
+ mContext.getPackageName(), mContext.getUserId()).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
+ // channel list assumed to be associated with the appropriate package & user id already.
+ private static NotificationChannel getChannelFromList(String channelId,
+ List<NotificationChannel> channels) {
+ if (channels == null) {
+ return null;
+ }
+ if (channelId == null) {
+ channelId = DEFAULT_CHANNEL_ID;
+ }
+ for (NotificationChannel channel : channels) {
+ if (channelId.equals(channel.getId())) {
+ return channel;
+ }
+ }
+ return null;
+ }
+
+ private static NotificationChannel getConversationChannelFromList(String channelId,
+ String conversationId, List<NotificationChannel> channels) {
+ if (channels == null) {
+ return null;
+ }
+ if (channelId == null) {
+ channelId = DEFAULT_CHANNEL_ID;
+ }
+ if (conversationId == null) {
+ return getChannelFromList(channelId, channels);
+ }
+ NotificationChannel parent = null;
+ for (NotificationChannel channel : channels) {
+ if (conversationId.equals(channel.getConversationId())
+ && channelId.equals(channel.getParentChannelId())) {
+ return channel;
+ } else if (channelId.equals(channel.getId())) {
+ parent = channel;
+ }
+ }
+ return parent;
+ }
+
/**
* Deletes the given notification channel.
*
@@ -1328,6 +1395,71 @@ public class NotificationManager {
}
}
+ private static final String NOTIFICATION_CHANNEL_CACHE_API = "getNotificationChannel";
+ private static final String NOTIFICATION_CHANNEL_LIST_CACHE_NAME = "getNotificationChannels";
+ private static final int NOTIFICATION_CHANNEL_CACHE_SIZE = 10;
+
+ private final IpcDataCache.QueryHandler<NotificationChannelQuery, List<NotificationChannel>>
+ mNotificationChannelListQueryHandler = new IpcDataCache.QueryHandler<>() {
+ @Override
+ public List<NotificationChannel> apply(NotificationChannelQuery query) {
+ INotificationManager service = service();
+ try {
+ return service.getNotificationChannels(query.callingPkg,
+ query.targetPkg, query.userId).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean shouldBypassCache(@NonNull NotificationChannelQuery query) {
+ // Other locations should also not be querying the cache in the first place if
+ // the flag is not enabled, but this is an extra precaution.
+ if (!Flags.nmBinderPerfCacheChannels()) {
+ Log.wtf(TAG,
+ "shouldBypassCache called when nm_binder_perf_cache_channels off");
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private final IpcDataCache<NotificationChannelQuery, List<NotificationChannel>>
+ mNotificationChannelListCache =
+ new IpcDataCache<>(NOTIFICATION_CHANNEL_CACHE_SIZE, IpcDataCache.MODULE_SYSTEM,
+ NOTIFICATION_CHANNEL_CACHE_API, NOTIFICATION_CHANNEL_LIST_CACHE_NAME,
+ mNotificationChannelListQueryHandler);
+
+ private record NotificationChannelQuery(
+ String callingPkg,
+ String targetPkg,
+ int userId) {}
+
+ /**
+ * @hide
+ */
+ public static void invalidateNotificationChannelCache() {
+ if (Flags.nmBinderPerfCacheChannels()) {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ NOTIFICATION_CHANNEL_CACHE_API);
+ } else {
+ // if we are here, we have failed to flag something
+ Log.wtf(TAG, "invalidateNotificationChannelCache called without flag");
+ }
+ }
+
+ /**
+ * For testing only: running tests with a cache requires marking the cache's property for
+ * testing, as test APIs otherwise cannot invalidate the cache. This must be called after
+ * calling PropertyInvalidatedCache.setTestMode(true).
+ * @hide
+ */
+ @VisibleForTesting
+ public void setChannelCacheToTestMode() {
+ mNotificationChannelListCache.testPropertyName();
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a2fddb045179..c50452157d74 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4354,20 +4354,24 @@ public class DevicePolicyManager {
}
/**
- * Indicates that app functions are not controlled by policy.
+ * Indicates that {@link android.app.appfunctions.AppFunctionManager} is not controlled by
+ * policy.
*
* <p>If no admin set this policy, it means appfunctions are enabled.
*/
@FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
public static final int APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY = 0;
- /** Indicates that app functions are controlled and disabled by a policy. */
+ /** Indicates that {@link android.app.appfunctions.AppFunctionManager} is controlled and
+ * disabled by policy, i.e. no apps in the current user are allowed to expose app functions.
+ */
@FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
public static final int APP_FUNCTIONS_DISABLED = 1;
/**
- * Indicates that app functions are controlled and disabled by a policy for cross profile
- * interactions only.
+ * Indicates that {@link android.app.appfunctions.AppFunctionManager} is controlled and
+ * disabled by a policy for cross profile interactions only, i.e. app functions exposed by apps
+ * in the current user can only be invoked within the same user.
*
* <p>This is different from {@link #APP_FUNCTIONS_DISABLED} in that it only disables cross
* profile interactions (even if the caller has permissions required to interact across users).
@@ -4388,7 +4392,9 @@ public class DevicePolicyManager {
public @interface AppFunctionsPolicy {}
/**
- * Sets the app functions policy which controls app functions operations on the device.
+ * Sets the {@link android.app.appfunctions.AppFunctionManager} policy which controls app
+ * functions operations on the device. An app function is a piece of functionality that apps
+ * expose to the system for cross-app orchestration.
*
* <p>This function can only be called by a device owner, a profile owner or holders of the
* permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_FUNCTIONS}.
@@ -4414,7 +4420,7 @@ public class DevicePolicyManager {
}
/**
- * Returns the current app functions policy.
+ * Returns the current {@link android.app.appfunctions.AppFunctionManager} policy.
*
* <p>The returned policy will be the current resolved policy rather than the policy set by the
* calling admin.
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index d86f1d841d33..8e48b4e56570 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -28,6 +28,7 @@ import android.annotation.SdkConstant;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.SigningInfo;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -78,10 +79,10 @@ public abstract class AppFunctionService extends Service {
void perform(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
+ @NonNull SigningInfo callingPackageSigningInfo,
@NonNull CancellationSignal cancellationSignal,
@NonNull
- OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
- callback);
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback);
}
/** @hide */
@@ -93,6 +94,7 @@ public abstract class AppFunctionService extends Service {
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
+ @NonNull SigningInfo callingPackageSigningInfo,
@NonNull ICancellationCallback cancellationCallback,
@NonNull IExecuteAppFunctionCallback callback) {
if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
@@ -105,6 +107,7 @@ public abstract class AppFunctionService extends Service {
onExecuteFunction.perform(
request,
callingPackage,
+ callingPackageSigningInfo,
buildCancellationSignal(cancellationCallback),
new OutcomeReceiver<>() {
@Override
@@ -154,15 +157,17 @@ public abstract class AppFunctionService extends Service {
/**
* Called by the system to execute a specific app function.
*
- * <p>This method is triggered when the system requests your AppFunctionService to handle a
- * particular function you have registered and made available.
+ * <p>This method is the entry point for handling all app function requests in an app. When the
+ * system needs your AppFunctionService to perform a function, it will invoke this method.
*
- * <p>To ensure proper routing of function requests, assign a unique identifier to each
- * function. This identifier doesn't need to be globally unique, but it must be unique within
- * your app. For example, a function to order food could be identified as "orderFood". In most
- * cases this identifier should come from the ID automatically generated by the AppFunctions
- * SDK. You can determine the specific function to invoke by calling {@link
- * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+ * <p>Each function you've registered is identified by a unique identifier. This identifier
+ * doesn't need to be globally unique, but it must be unique within your app. For example, a
+ * function to order food could be identified as "orderFood". In most cases, this identifier is
+ * automatically generated by the AppFunctions SDK.
+ *
+ * <p>You can determine which function to execute by calling {@link
+ * ExecuteAppFunctionRequest#getFunctionIdentifier()}. This allows your service to route the
+ * incoming request to the appropriate logic for handling the specific function.
*
* <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
* thread and dispatch the result with the given callback. You should always report back the
@@ -173,6 +178,8 @@ public abstract class AppFunctionService extends Service {
*
* @param request The function execution request.
* @param callingPackage The package name of the app that is requesting the execution.
+ * @param callingPackageSigningInfo The signing information of the app that is requesting the
+ * execution.
* @param cancellationSignal A signal to cancel the execution.
* @param callback A callback to report back the result or error.
*/
@@ -180,10 +187,9 @@ public abstract class AppFunctionService extends Service {
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
+ @NonNull SigningInfo callingPackageSigningInfo,
@NonNull CancellationSignal cancellationSignal,
- @NonNull
- OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
- callback);
+ @NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback);
/**
* Returns result codes from throwable.
diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl
index bf935d2a102b..78bcb7f66eb1 100644
--- a/core/java/android/app/appfunctions/IAppFunctionService.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl
@@ -35,12 +35,15 @@ oneway interface IAppFunctionService {
*
* @param request the function execution request.
* @param callingPackage The package name of the app that is requesting the execution.
+ * @param callingPackageSigningInfo The signing information of the app that is requesting the
+ * execution.
* @param cancellationCallback a callback to send back the cancellation transport.
* @param callback a callback to report back the result.
*/
void executeAppFunction(
in ExecuteAppFunctionRequest request,
in String callingPackage,
+ in android.content.pm.SigningInfo callingPackageSigningInfo,
in ICancellationCallback cancellationCallback,
in IExecuteAppFunctionCallback callback
);
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index 4dee15988795..929f66079a3d 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -55,7 +55,8 @@ public final class TimeZoneCapabilities implements Parcelable {
* The user the capabilities are for. This is used for object equality and debugging but there
* is no accessor.
*/
- @NonNull private final UserHandle mUserHandle;
+ @NonNull
+ private final UserHandle mUserHandle;
private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
/**
@@ -69,6 +70,7 @@ public final class TimeZoneCapabilities implements Parcelable {
private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
private final @CapabilityState int mSetManualTimeZoneCapability;
+ private final @CapabilityState int mConfigureNotificationsEnabledCapability;
private TimeZoneCapabilities(@NonNull Builder builder) {
this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
@@ -78,6 +80,8 @@ public final class TimeZoneCapabilities implements Parcelable {
this.mConfigureGeoDetectionEnabledCapability =
builder.mConfigureGeoDetectionEnabledCapability;
this.mSetManualTimeZoneCapability = builder.mSetManualTimeZoneCapability;
+ this.mConfigureNotificationsEnabledCapability =
+ builder.mConfigureNotificationsEnabledCapability;
}
@NonNull
@@ -88,6 +92,7 @@ public final class TimeZoneCapabilities implements Parcelable {
.setUseLocationEnabled(in.readBoolean())
.setConfigureGeoDetectionEnabledCapability(in.readInt())
.setSetManualTimeZoneCapability(in.readInt())
+ .setConfigureNotificationsEnabledCapability(in.readInt())
.build();
}
@@ -98,6 +103,7 @@ public final class TimeZoneCapabilities implements Parcelable {
dest.writeBoolean(mUseLocationEnabled);
dest.writeInt(mConfigureGeoDetectionEnabledCapability);
dest.writeInt(mSetManualTimeZoneCapability);
+ dest.writeInt(mConfigureNotificationsEnabledCapability);
}
/**
@@ -117,8 +123,8 @@ public final class TimeZoneCapabilities implements Parcelable {
*
* Not part of the SDK API because it is intended for use by SettingsUI, which can display
* text about needing it to be on for location-based time zone detection.
- * @hide
*
+ * @hide
*/
public boolean isUseLocationEnabled() {
return mUseLocationEnabled;
@@ -148,6 +154,18 @@ public final class TimeZoneCapabilities implements Parcelable {
}
/**
+ * Returns the capability state associated with the user's ability to modify the time zone
+ * notification setting. The setting can be updated via {@link
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
+ *
+ * @hide
+ */
+ @CapabilityState
+ public int getConfigureNotificationsEnabledCapability() {
+ return mConfigureNotificationsEnabledCapability;
+ }
+
+ /**
* Tries to create a new {@link TimeZoneConfiguration} from the {@code config} and the set of
* {@code requestedChanges}, if {@code this} capabilities allow. The new configuration is
* returned. If the capabilities do not permit one or more of the requested changes then {@code
@@ -174,6 +192,12 @@ public final class TimeZoneCapabilities implements Parcelable {
newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
}
+ if (requestedChanges.hasIsNotificationsEnabled()) {
+ if (this.getConfigureNotificationsEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
+ return null;
+ }
+ newConfigBuilder.setNotificationsEnabled(requestedChanges.areNotificationsEnabled());
+ }
return newConfigBuilder.build();
}
@@ -197,13 +221,16 @@ public final class TimeZoneCapabilities implements Parcelable {
&& mUseLocationEnabled == that.mUseLocationEnabled
&& mConfigureGeoDetectionEnabledCapability
== that.mConfigureGeoDetectionEnabledCapability
- && mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability;
+ && mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability
+ && mConfigureNotificationsEnabledCapability
+ == that.mConfigureNotificationsEnabledCapability;
}
@Override
public int hashCode() {
return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
- mConfigureGeoDetectionEnabledCapability, mSetManualTimeZoneCapability);
+ mConfigureGeoDetectionEnabledCapability, mSetManualTimeZoneCapability,
+ mConfigureNotificationsEnabledCapability);
}
@Override
@@ -216,6 +243,8 @@ public final class TimeZoneCapabilities implements Parcelable {
+ ", mConfigureGeoDetectionEnabledCapability="
+ mConfigureGeoDetectionEnabledCapability
+ ", mSetManualTimeZoneCapability=" + mSetManualTimeZoneCapability
+ + ", mConfigureNotificationsEnabledCapability="
+ + mConfigureNotificationsEnabledCapability
+ '}';
}
@@ -226,11 +255,13 @@ public final class TimeZoneCapabilities implements Parcelable {
*/
public static class Builder {
- @NonNull private UserHandle mUserHandle;
+ @NonNull
+ private UserHandle mUserHandle;
private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
private Boolean mUseLocationEnabled;
private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
private @CapabilityState int mSetManualTimeZoneCapability;
+ private @CapabilityState int mConfigureNotificationsEnabledCapability;
public Builder(@NonNull UserHandle userHandle) {
mUserHandle = Objects.requireNonNull(userHandle);
@@ -240,12 +271,14 @@ public final class TimeZoneCapabilities implements Parcelable {
Objects.requireNonNull(capabilitiesToCopy);
mUserHandle = capabilitiesToCopy.mUserHandle;
mConfigureAutoDetectionEnabledCapability =
- capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
+ capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
mUseLocationEnabled = capabilitiesToCopy.mUseLocationEnabled;
mConfigureGeoDetectionEnabledCapability =
- capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
+ capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
mSetManualTimeZoneCapability =
- capabilitiesToCopy.mSetManualTimeZoneCapability;
+ capabilitiesToCopy.mSetManualTimeZoneCapability;
+ mConfigureNotificationsEnabledCapability =
+ capabilitiesToCopy.mConfigureNotificationsEnabledCapability;
}
/** Sets the value for the "configure automatic time zone detection enabled" capability. */
@@ -274,6 +307,14 @@ public final class TimeZoneCapabilities implements Parcelable {
return this;
}
+ /**
+ * Sets the value for the "configure time notifications enabled" capability.
+ */
+ public Builder setConfigureNotificationsEnabledCapability(@CapabilityState int value) {
+ this.mConfigureNotificationsEnabledCapability = value;
+ return this;
+ }
+
/** Returns the {@link TimeZoneCapabilities}. */
@NonNull
public TimeZoneCapabilities build() {
@@ -283,7 +324,9 @@ public final class TimeZoneCapabilities implements Parcelable {
verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
"configureGeoDetectionEnabledCapability");
verifyCapabilitySet(mSetManualTimeZoneCapability,
- "mSetManualTimeZoneCapability");
+ "setManualTimeZoneCapability");
+ verifyCapabilitySet(mConfigureNotificationsEnabledCapability,
+ "configureNotificationsEnabledCapability");
return new TimeZoneCapabilities(this);
}
diff --git a/core/java/android/app/time/TimeZoneConfiguration.java b/core/java/android/app/time/TimeZoneConfiguration.java
index 7403c129f4dc..68c090f6dde3 100644
--- a/core/java/android/app/time/TimeZoneConfiguration.java
+++ b/core/java/android/app/time/TimeZoneConfiguration.java
@@ -62,7 +62,8 @@ public final class TimeZoneConfiguration implements Parcelable {
*
* @hide
*/
- @StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
+ @StringDef({SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED,
+ SETTING_NOTIFICATIONS_ENABLED})
@Retention(RetentionPolicy.SOURCE)
@interface Setting {}
@@ -74,6 +75,10 @@ public final class TimeZoneConfiguration implements Parcelable {
@Setting
private static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+ /** See {@link TimeZoneConfiguration#areNotificationsEnabled()} for details. */
+ @Setting
+ private static final String SETTING_NOTIFICATIONS_ENABLED = "notificationsEnabled";
+
@NonNull private final Bundle mBundle;
private TimeZoneConfiguration(Builder builder) {
@@ -98,7 +103,8 @@ public final class TimeZoneConfiguration implements Parcelable {
*/
public boolean isComplete() {
return hasIsAutoDetectionEnabled()
- && hasIsGeoDetectionEnabled();
+ && hasIsGeoDetectionEnabled()
+ && hasIsNotificationsEnabled();
}
/**
@@ -128,8 +134,7 @@ public final class TimeZoneConfiguration implements Parcelable {
/**
* Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
* controls whether the device can use geolocation to determine time zone. This value may only
- * be used by Android under some circumstances. For example, it is not used when
- * {@link #isGeoDetectionEnabled()} is {@code false}.
+ * be used by Android under some circumstances.
*
* <p>See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabledCapability()} for how to
* tell if the setting is meaningful for the current user at this time.
@@ -150,6 +155,32 @@ public final class TimeZoneConfiguration implements Parcelable {
return mBundle.containsKey(SETTING_GEO_DETECTION_ENABLED);
}
+ /**
+ * Returns the value of the {@link #SETTING_NOTIFICATIONS_ENABLED} setting. This controls
+ * whether the device can send time and time zone related notifications. This value may only
+ * be used by Android under some circumstances.
+ *
+ * <p>See {@link TimeZoneCapabilities#getConfigureNotificationsEnabledCapability()} ()} for how
+ * to tell if the setting is meaningful for the current user at this time.
+ *
+ * @throws IllegalStateException if the setting is not present
+ *
+ * @hide
+ */
+ public boolean areNotificationsEnabled() {
+ enforceSettingPresent(SETTING_NOTIFICATIONS_ENABLED);
+ return mBundle.getBoolean(SETTING_NOTIFICATIONS_ENABLED);
+ }
+
+ /**
+ * Returns {@code true} if the {@link #areNotificationsEnabled()} setting is present.
+ *
+ * @hide
+ */
+ public boolean hasIsNotificationsEnabled() {
+ return mBundle.containsKey(SETTING_NOTIFICATIONS_ENABLED);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -244,6 +275,17 @@ public final class TimeZoneConfiguration implements Parcelable {
return this;
}
+ /**
+ * Sets the state of the {@link #SETTING_NOTIFICATIONS_ENABLED} setting. *
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setNotificationsEnabled(boolean enabled) {
+ this.mBundle.putBoolean(SETTING_NOTIFICATIONS_ENABLED, enabled);
+ return this;
+ }
+
/** Returns the {@link TimeZoneConfiguration}. */
@NonNull
public TimeZoneConfiguration build() {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e3e10388754c..350048df3112 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12394,14 +12394,30 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
public void collectExtraIntentKeys() {
+ collectExtraIntentKeys(false);
+ }
+
+ /**
+ * Collects keys in the extra bundle whose value are intents.
+ * With these keys collected on the client side, the system server would only unparcel values
+ * of these keys and create IntentCreatorToken for them.
+ * This method could also be called from the system server side as a catch all safty net in case
+ * these keys are not collected on the client side. In that case, call it with forceUnparcel set
+ * to true since everything is parceled on the system server side.
+ *
+ * @param forceUnparcel if it is true, unparcel everything to determine if an object is an
+ * intent. Otherwise, do not unparcel anything.
+ * @hide
+ */
+ public void collectExtraIntentKeys(boolean forceUnparcel) {
if (preventIntentRedirect()) {
- collectNestedIntentKeysRecur(new ArraySet<>());
+ collectNestedIntentKeysRecur(new ArraySet<>(), forceUnparcel);
}
}
- private void collectNestedIntentKeysRecur(Set<Intent> visited) {
+ private void collectNestedIntentKeysRecur(Set<Intent> visited, boolean forceUnparcel) {
addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
- if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
+ if (mExtras != null && (forceUnparcel || !mExtras.isParcelled()) && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
Object value;
try {
@@ -12410,23 +12426,25 @@ public class Intent implements Parcelable, Cloneable {
// It is okay to not collect a parceled intent since it would have been
// coming from another process and collected by its containing intent already
// in that process.
- if (!mExtras.isValueParceled(key)) {
+ if (forceUnparcel || !mExtras.isValueParceled(key)) {
value = mExtras.get(key);
} else {
value = null;
}
} catch (BadParcelableException e) {
- // This probably would never happen. But just in case, simply ignore it since
- // it is not an intent anyway.
+ // This may still happen if the keys are collected on the system server side, in
+ // which case, we will try to unparcel everything. If this happens, simply
+ // ignore it since it is not an intent anyway.
value = null;
}
if (value instanceof Intent intent) {
handleNestedIntent(intent, visited, new NestedIntentKey(
- NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
+ NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0),
+ forceUnparcel);
} else if (value instanceof Parcelable[] parcelables) {
- handleParcelableArray(parcelables, key, visited);
+ handleParcelableArray(parcelables, key, visited, forceUnparcel);
} else if (value instanceof ArrayList<?> parcelables) {
- handleParcelableList(parcelables, key, visited);
+ handleParcelableList(parcelables, key, visited, forceUnparcel);
}
}
}
@@ -12436,13 +12454,15 @@ public class Intent implements Parcelable, Cloneable {
Intent intent = mClipData.getItemAt(i).mIntent;
if (intent != null && !visited.contains(intent)) {
handleNestedIntent(intent, visited, new NestedIntentKey(
- NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i));
+ NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i),
+ forceUnparcel);
}
}
}
}
- private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) {
+ private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key,
+ boolean forceUnparcel) {
if (mCreatorTokenInfo == null) {
mCreatorTokenInfo = new CreatorTokenInfo();
}
@@ -12452,24 +12472,28 @@ public class Intent implements Parcelable, Cloneable {
mCreatorTokenInfo.mNestedIntentKeys.add(key);
if (!visited.contains(intent)) {
visited.add(intent);
- intent.collectNestedIntentKeysRecur(visited);
+ intent.collectNestedIntentKeysRecur(visited, forceUnparcel);
}
}
- private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) {
+ private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited,
+ boolean forceUnparcel) {
for (int i = 0; i < parcelables.length; i++) {
if (parcelables[i] instanceof Intent intent && !visited.contains(intent)) {
handleNestedIntent(intent, visited, new NestedIntentKey(
- NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i));
+ NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i),
+ forceUnparcel);
}
}
}
- private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited) {
+ private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited,
+ boolean forceUnparcel) {
for (int i = 0; i < parcelables.size(); i++) {
if (parcelables.get(i) instanceof Intent intent && !visited.contains(intent)) {
handleNestedIntent(intent, visited, new NestedIntentKey(
- NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i));
+ NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i),
+ forceUnparcel);
}
}
}
diff --git a/core/java/android/content/pm/SigningInfo.aidl b/core/java/android/content/pm/SigningInfo.aidl
new file mode 100644
index 000000000000..bc986d1b214b
--- /dev/null
+++ b/core/java/android/content/pm/SigningInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.content.pm;
+
+parcelable SigningInfo; \ No newline at end of file
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index b938aac811fd..f538e9ffffdd 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -367,7 +367,7 @@ public final class ApkAssets {
/** @hide */
public @NonNull String getDebugName() {
synchronized (this) {
- return nativeGetDebugName(mNativePtr);
+ return mNativePtr == 0 ? "<destroyed>" : nativeGetDebugName(mNativePtr);
}
}
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 71702d996883..25cdc508fdce 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -137,6 +137,8 @@ public class HubEndpoint {
serviceDescriptor,
mLifecycleCallback.onSessionOpenRequest(
initiator, serviceDescriptor)));
+ } else {
+ invokeCallbackFinished();
}
}
@@ -163,6 +165,8 @@ public class HubEndpoint {
+ result.getReason());
rejectSession(sessionId);
}
+
+ invokeCallbackFinished();
}
private void acceptSession(
@@ -249,7 +253,12 @@ public class HubEndpoint {
activeSession.setOpened();
if (mLifecycleCallback != null) {
mLifecycleCallbackExecutor.execute(
- () -> mLifecycleCallback.onSessionOpened(activeSession));
+ () -> {
+ mLifecycleCallback.onSessionOpened(activeSession);
+ invokeCallbackFinished();
+ });
+ } else {
+ invokeCallbackFinished();
}
}
@@ -278,7 +287,10 @@ public class HubEndpoint {
synchronized (mLock) {
mActiveSessions.remove(sessionId);
}
+ invokeCallbackFinished();
});
+ } else {
+ invokeCallbackFinished();
}
}
@@ -323,8 +335,17 @@ public class HubEndpoint {
e.rethrowFromSystemServer();
}
}
+ invokeCallbackFinished();
});
}
+
+ private void invokeCallbackFinished() {
+ try {
+ mServiceToken.onCallbackFinished();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
};
/** Binder returned from system service, non-null while registered. */
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
index 44f80c819e83..eb1255c06094 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -94,4 +94,10 @@ interface IContextHubEndpoint {
*/
@EnforcePermission("ACCESS_CONTEXT_HUB")
void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode);
+
+ /**
+ * Invoked when a callback from IContextHubEndpointCallback finishes.
+ */
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ void onCallbackFinished();
}
diff --git a/core/java/android/hardware/input/InputGestureData.java b/core/java/android/hardware/input/InputGestureData.java
index f41550f6061e..75c652c973e4 100644
--- a/core/java/android/hardware/input/InputGestureData.java
+++ b/core/java/android/hardware/input/InputGestureData.java
@@ -48,27 +48,7 @@ public final class InputGestureData {
/** Returns the trigger information for this input gesture */
public Trigger getTrigger() {
- switch (mInputGestureData.trigger.getTag()) {
- case AidlInputGestureData.Trigger.Tag.key: {
- AidlInputGestureData.KeyTrigger trigger = mInputGestureData.trigger.getKey();
- if (trigger == null) {
- throw new RuntimeException("InputGestureData is corrupted, null key trigger!");
- }
- return createKeyTrigger(trigger.keycode, trigger.modifierState);
- }
- case AidlInputGestureData.Trigger.Tag.touchpadGesture: {
- AidlInputGestureData.TouchpadGestureTrigger trigger =
- mInputGestureData.trigger.getTouchpadGesture();
- if (trigger == null) {
- throw new RuntimeException(
- "InputGestureData is corrupted, null touchpad trigger!");
- }
- return createTouchpadTrigger(trigger.gestureType);
- }
- default:
- throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!");
-
- }
+ return createTriggerFromAidlTrigger(mInputGestureData.trigger);
}
/** Returns the action to perform for this input gesture */
@@ -147,18 +127,7 @@ public final class InputGestureData {
"No app launch data for system action launch application");
}
AidlInputGestureData data = new AidlInputGestureData();
- data.trigger = new AidlInputGestureData.Trigger();
- if (mTrigger instanceof KeyTrigger keyTrigger) {
- data.trigger.setKey(new AidlInputGestureData.KeyTrigger());
- data.trigger.getKey().keycode = keyTrigger.getKeycode();
- data.trigger.getKey().modifierState = keyTrigger.getModifierState();
- } else if (mTrigger instanceof TouchpadTrigger touchpadTrigger) {
- data.trigger.setTouchpadGesture(new AidlInputGestureData.TouchpadGestureTrigger());
- data.trigger.getTouchpadGesture().gestureType =
- touchpadTrigger.getTouchpadGestureType();
- } else {
- throw new IllegalArgumentException("Invalid trigger type!");
- }
+ data.trigger = mTrigger.getAidlTrigger();
data.gestureType = mKeyGestureType;
if (mAppLaunchData != null) {
if (mAppLaunchData instanceof AppLaunchData.CategoryData categoryData) {
@@ -198,6 +167,7 @@ public final class InputGestureData {
}
public interface Trigger {
+ AidlInputGestureData.Trigger getAidlTrigger();
}
/** Creates a input gesture trigger based on a key press */
@@ -210,85 +180,128 @@ public final class InputGestureData {
return new TouchpadTrigger(touchpadGestureType);
}
+ public static Trigger createTriggerFromAidlTrigger(AidlInputGestureData.Trigger aidlTrigger) {
+ switch (aidlTrigger.getTag()) {
+ case AidlInputGestureData.Trigger.Tag.key: {
+ AidlInputGestureData.KeyTrigger trigger = aidlTrigger.getKey();
+ if (trigger == null) {
+ throw new RuntimeException("aidlTrigger is corrupted, null key trigger!");
+ }
+ return new KeyTrigger(trigger);
+ }
+ case AidlInputGestureData.Trigger.Tag.touchpadGesture: {
+ AidlInputGestureData.TouchpadGestureTrigger trigger =
+ aidlTrigger.getTouchpadGesture();
+ if (trigger == null) {
+ throw new RuntimeException(
+ "aidlTrigger is corrupted, null touchpad trigger!");
+ }
+ return new TouchpadTrigger(trigger);
+ }
+ default:
+ throw new RuntimeException("aidlTrigger is corrupted, invalid trigger type!");
+
+ }
+ }
+
/** Key based input gesture trigger */
public static class KeyTrigger implements Trigger {
- private static final int SHORTCUT_META_MASK =
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON
- | KeyEvent.META_SHIFT_ON;
- private final int mKeycode;
- private final int mModifierState;
+
+ AidlInputGestureData.KeyTrigger mAidlKeyTrigger;
+
+ private KeyTrigger(@NonNull AidlInputGestureData.KeyTrigger aidlKeyTrigger) {
+ mAidlKeyTrigger = aidlKeyTrigger;
+ }
private KeyTrigger(int keycode, int modifierState) {
if (keycode <= KeyEvent.KEYCODE_UNKNOWN || keycode > KeyEvent.getMaxKeyCode()) {
throw new IllegalArgumentException("Invalid keycode = " + keycode);
}
- mKeycode = keycode;
- mModifierState = modifierState;
+ mAidlKeyTrigger = new AidlInputGestureData.KeyTrigger();
+ mAidlKeyTrigger.keycode = keycode;
+ mAidlKeyTrigger.modifierState = modifierState;
}
public int getKeycode() {
- return mKeycode;
+ return mAidlKeyTrigger.keycode;
}
public int getModifierState() {
- return mModifierState;
+ return mAidlKeyTrigger.modifierState;
+ }
+
+ public AidlInputGestureData.Trigger getAidlTrigger() {
+ AidlInputGestureData.Trigger trigger = new AidlInputGestureData.Trigger();
+ trigger.setKey(mAidlKeyTrigger);
+ return trigger;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof KeyTrigger that)) return false;
- return mKeycode == that.mKeycode && mModifierState == that.mModifierState;
+ return Objects.equals(mAidlKeyTrigger, that.mAidlKeyTrigger);
}
@Override
public int hashCode() {
- return Objects.hash(mKeycode, mModifierState);
+ return mAidlKeyTrigger.hashCode();
}
@Override
public String toString() {
return "KeyTrigger{" +
- "mKeycode=" + KeyEvent.keyCodeToString(mKeycode) +
- ", mModifierState=" + mModifierState +
+ "mKeycode=" + KeyEvent.keyCodeToString(mAidlKeyTrigger.keycode) +
+ ", mModifierState=" + mAidlKeyTrigger.modifierState +
'}';
}
}
/** Touchpad based input gesture trigger */
public static class TouchpadTrigger implements Trigger {
- private final int mTouchpadGestureType;
+ AidlInputGestureData.TouchpadGestureTrigger mAidlTouchpadTrigger;
+
+ private TouchpadTrigger(
+ @NonNull AidlInputGestureData.TouchpadGestureTrigger aidlTouchpadTrigger) {
+ mAidlTouchpadTrigger = aidlTouchpadTrigger;
+ }
private TouchpadTrigger(int touchpadGestureType) {
if (touchpadGestureType != TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP) {
throw new IllegalArgumentException(
"Invalid touchpadGestureType = " + touchpadGestureType);
}
- mTouchpadGestureType = touchpadGestureType;
+ mAidlTouchpadTrigger = new AidlInputGestureData.TouchpadGestureTrigger();
+ mAidlTouchpadTrigger.gestureType = touchpadGestureType;
}
public int getTouchpadGestureType() {
- return mTouchpadGestureType;
+ return mAidlTouchpadTrigger.gestureType;
+ }
+
+ public AidlInputGestureData.Trigger getAidlTrigger() {
+ AidlInputGestureData.Trigger trigger = new AidlInputGestureData.Trigger();
+ trigger.setTouchpadGesture(mAidlTouchpadTrigger);
+ return trigger;
}
@Override
public String toString() {
return "TouchpadTrigger{" +
- "mTouchpadGestureType=" + mTouchpadGestureType +
+ "mTouchpadGestureType=" + mAidlTouchpadTrigger.gestureType +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- TouchpadTrigger that = (TouchpadTrigger) o;
- return mTouchpadGestureType == that.mTouchpadGestureType;
+ if (!(o instanceof TouchpadTrigger that)) return false;
+ return Objects.equals(mAidlTouchpadTrigger, that.mAidlTouchpadTrigger);
}
@Override
public int hashCode() {
- return Objects.hashCode(mTouchpadGestureType);
+ return mAidlTouchpadTrigger.hashCode();
}
}
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 66d073fa791e..cb1e0161441f 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -129,6 +129,7 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT = 79;
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP = 80;
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN = 81;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 82;
public static final int FLAG_CANCELLED = 1;
@@ -225,6 +226,7 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT,
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP,
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -807,6 +809,8 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP";
case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN:
return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN";
+ case KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ return "KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS";
default:
return Integer.toHexString(value);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 1801df048b3e..2a5666cbe83c 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -615,6 +615,7 @@ public final class PowerManager {
WAKE_REASON_WAKE_KEY,
WAKE_REASON_WAKE_MOTION,
WAKE_REASON_HDMI,
+ WAKE_REASON_LID,
WAKE_REASON_DISPLAY_GROUP_ADDED,
WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
WAKE_REASON_UNFOLD_DEVICE,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2656a7b58c8d..11dddfb24ad5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9319,6 +9319,16 @@ public final class Settings {
"accessibility_autoclick_delay";
/**
+ * Integer setting specifying the autoclick cursor area size (the radius of the autoclick
+ * ring indicator) when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
+ *
+ * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+ * @hide
+ */
+ public static final String ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE =
+ "accessibility_autoclick_cursor_area_size";
+
+ /**
* Whether or not larger size icons are used for the pointer of mouse/trackpad for
* accessibility.
* (0 = false, 1 = true)
@@ -13318,6 +13328,16 @@ public final class Settings {
public static final String AUTO_TIME_ZONE_EXPLICIT = "auto_time_zone_explicit";
/**
+ * Value to specify if the device should send notifications when {@link #AUTO_TIME_ZONE} is
+ * on and the device's time zone changes.
+ *
+ * <p>1=yes, 0=no.
+ *
+ * @hide
+ */
+ public static final String TIME_ZONE_NOTIFICATIONS = "time_zone_notifications";
+
+ /**
* URI for the car dock "in" event sound.
* @hide
*/
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index e254bf3e016f..d53b98c65f9a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -77,7 +77,7 @@ public abstract class Layout {
private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0f;
private static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP = 5f;
// since we're not using soft light yet, this needs to be much lower than the spec'd 0.8
- private static final float HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE = 0.5f;
+ private static final float HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE = 0.7f;
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index fd57aec4180b..544f42b9acfa 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -148,6 +148,15 @@ public final class AccessibilityManager {
/** @hide */
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
+ /** @hide */
+ public static final int AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT = 60;
+
+ /** @hide */
+ public static final int AUTOCLICK_CURSOR_AREA_SIZE_MIN = 20;
+
+ /** @hide */
+ public static final int AUTOCLICK_CURSOR_AREA_SIZE_MAX = 100;
+
/**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 20e3f6b93bd0..2911b0a6643d 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -464,7 +464,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
* Returns false if the legacy back behavior should be used.
*/
public boolean isOnBackInvokedCallbackEnabled() {
- return isOnBackInvokedCallbackEnabled(mChecker.getContext());
+ final Context hostContext = mChecker.getContext();
+ if (hostContext == null) {
+ Log.w(TAG, "OnBackInvokedCallback is disabled, host context is removed!");
+ return false;
+ }
+ return isOnBackInvokedCallbackEnabled(hostContext);
}
/**
@@ -695,7 +700,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
*/
public boolean checkApplicationCallbackRegistration(int priority,
OnBackInvokedCallback callback) {
- if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(getContext())
+ final Context hostContext = getContext();
+ if (hostContext == null) {
+ Log.w(TAG, "OnBackInvokedCallback is disabled, host context is removed!");
+ return false;
+ }
+ if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(hostContext)
&& !(callback instanceof CompatOnBackInvokedCallback)) {
Log.w(TAG,
"OnBackInvokedCallback is not enabled for the application."
@@ -720,7 +730,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
return true;
}
- private Context getContext() {
+ @Nullable private Context getContext() {
return mContext.get();
}
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 4241ec776465..be0b4fea459c 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -478,10 +478,10 @@ flag {
}
flag {
- name: "enable_multiple_desktops"
+ name: "enable_multiple_desktops_frontend"
namespace: "lse_desktop_experience"
- description: "Enable multiple desktop sessions for desktop windowing."
- bug: "379158791"
+ description: "Enable multiple desktop sessions for desktop windowing (frontend)."
+ bug: "362720309"
}
flag {
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index 0b1ecf78d28c..d03bb5c3cb17 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
@@ -351,4 +352,24 @@ public final class AccessibilityUtils {
}
return result;
}
+
+ /** Returns the {@link ComponentName} of an installed accessibility service by label. */
+ @Nullable
+ public static ComponentName getInstalledAccessibilityServiceComponentNameByLabel(
+ Context context, String label) {
+ AccessibilityManager accessibilityManager =
+ context.getSystemService(AccessibilityManager.class);
+ List<AccessibilityServiceInfo> serviceInfos =
+ accessibilityManager.getInstalledAccessibilityServiceList();
+
+ for (AccessibilityServiceInfo service : serviceInfos) {
+ final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ if (label.equals(serviceInfo.loadLabel(context.getPackageManager()).toString())
+ && (serviceInfo.applicationInfo.isSystemApp()
+ || serviceInfo.applicationInfo.isUpdatedSystemApp())) {
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 4aebde536dcf..972c2ea403e0 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -49,6 +49,7 @@ public class SystemNotificationChannels {
public static final String NETWORK_ALERTS = "NETWORK_ALERTS";
public static final String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
public static final String VPN = "VPN";
+ public static final String TIME = "TIME";
/**
* @deprecated Legacy device admin channel with low importance which is no longer used,
* Use the high importance {@link #DEVICE_ADMIN} channel instead.
@@ -67,6 +68,7 @@ public class SystemNotificationChannels {
@Deprecated public static final String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
public static final String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
public static final String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
+ public static final String ACCESSIBILITY_HEARING_DEVICE = "ACCESSIBILITY_HEARING_DEVICE";
public static final String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
public static final String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
@@ -145,6 +147,12 @@ public class SystemNotificationChannels {
NotificationManager.IMPORTANCE_LOW);
channelsList.add(vpn);
+ final NotificationChannel time = new NotificationChannel(
+ TIME,
+ context.getString(R.string.notification_channel_system_time),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channelsList.add(time);
+
final NotificationChannel deviceAdmin = new NotificationChannel(
DEVICE_ADMIN,
getDeviceAdminNotificationChannelName(context),
@@ -203,6 +211,13 @@ public class SystemNotificationChannels {
newFeaturePrompt.setBlockable(true);
channelsList.add(newFeaturePrompt);
+ final NotificationChannel accessibilityHearingDeviceChannel = new NotificationChannel(
+ ACCESSIBILITY_HEARING_DEVICE,
+ context.getString(R.string.notification_channel_accessibility_hearing_device),
+ NotificationManager.IMPORTANCE_HIGH);
+ accessibilityHearingDeviceChannel.setBlockable(true);
+ channelsList.add(accessibilityHearingDeviceChannel);
+
final NotificationChannel accessibilitySecurityPolicyChannel = new NotificationChannel(
ACCESSIBILITY_SECURITY_POLICY,
context.getString(R.string.notification_channel_accessibility_security_policy),
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 8cd7843fe1d9..f0b54937546b 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -31,6 +32,7 @@ import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.view.RemotableViewMethod;
import android.widget.ProgressBar;
import android.widget.RemoteViews;
@@ -40,14 +42,12 @@ import androidx.annotation.ColorInt;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import com.android.internal.widget.NotificationProgressDrawable.Part;
-import com.android.internal.widget.NotificationProgressDrawable.Point;
-import com.android.internal.widget.NotificationProgressDrawable.Segment;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -56,18 +56,25 @@ import java.util.TreeSet;
* represent Notification ProgressStyle progress, such as for ridesharing and navigation.
*/
@RemoteViews.RemoteView
-public final class NotificationProgressBar extends ProgressBar {
+public final class NotificationProgressBar extends ProgressBar implements
+ NotificationProgressDrawable.BoundsChangeListener {
private static final String TAG = "NotificationProgressBar";
private NotificationProgressDrawable mNotificationProgressDrawable;
+ private final Rect mProgressDrawableBounds = new Rect();
private NotificationProgressModel mProgressModel;
@Nullable
- private List<Part> mProgressDrawableParts = null;
+ private List<Part> mParts = null;
+
+ // List of drawable parts before segment splitting by process.
+ @Nullable
+ private List<NotificationProgressDrawable.Part> mProgressDrawableParts = null;
@Nullable
private Drawable mTracker = null;
+ private boolean mHasTrackerIcon = false;
/** @see R.styleable#NotificationProgressBar_trackerHeight */
private final int mTrackerHeight;
@@ -76,7 +83,13 @@ public final class NotificationProgressBar extends ProgressBar {
private final Matrix mMatrix = new Matrix();
private Matrix mTrackerDrawMatrix = null;
- private float mScale = 0;
+ private float mProgressFraction = 0;
+ /**
+ * The location of progress on the stretched and rescaled progress bar, in fraction. Used for
+ * calculating the tracker position. If stretching and rescaling is not needed, ==
+ * mProgressFraction.
+ */
+ private float mAdjustedProgressFraction = 0;
/** Indicates whether mTrackerPos needs to be recalculated before the tracker is drawn. */
private boolean mTrackerPosIsDirty = false;
@@ -104,12 +117,13 @@ public final class NotificationProgressBar extends ProgressBar {
try {
mNotificationProgressDrawable = getNotificationProgressDrawable();
+ mNotificationProgressDrawable.setBoundsChangeListener(this);
} catch (IllegalStateException ex) {
Log.e(TAG, "Can't get NotificationProgressDrawable", ex);
}
// Supports setting the tracker in xml, but ProgressStyle notifications set/override it
- // via {@code setProgressTrackerIcon}.
+ // via {@code #setProgressTrackerIcon}.
final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker);
setTracker(tracker);
@@ -137,20 +151,25 @@ public final class NotificationProgressBar extends ProgressBar {
final int indeterminateColor = mProgressModel.getIndeterminateColor();
setIndeterminateTintList(ColorStateList.valueOf(indeterminateColor));
} else {
+ // TODO: b/372908709 - maybe don't rerun the entire calculation every time the
+ // progress model is updated? For example, if the segments and parts aren't changed,
+ // there is no need to call `processAndConvertToViewParts` again.
+
final int progress = mProgressModel.getProgress();
final int progressMax = mProgressModel.getProgressMax();
- mProgressDrawableParts = processAndConvertToDrawableParts(mProgressModel.getSegments(),
+
+ mParts = processAndConvertToViewParts(mProgressModel.getSegments(),
mProgressModel.getPoints(),
progress,
- progressMax,
- mProgressModel.isStyledByProgress());
-
- if (mNotificationProgressDrawable != null) {
- mNotificationProgressDrawable.setParts(mProgressDrawableParts);
- }
+ progressMax);
setMax(progressMax);
setProgress(progress);
+
+ if (mNotificationProgressDrawable != null
+ && mNotificationProgressDrawable.getBounds().width() != 0) {
+ updateDrawableParts();
+ }
}
}
@@ -200,9 +219,7 @@ public final class NotificationProgressBar extends ProgressBar {
} else {
progressTrackerDrawable = null;
}
- return () -> {
- setTracker(progressTrackerDrawable);
- };
+ return () -> setTracker(progressTrackerDrawable);
}
private void setTracker(@Nullable Drawable tracker) {
@@ -226,8 +243,14 @@ public final class NotificationProgressBar extends ProgressBar {
final boolean trackerSizeChanged = trackerSizeChanged(tracker, mTracker);
mTracker = tracker;
- if (mNotificationProgressDrawable != null) {
- mNotificationProgressDrawable.setHasTrackerIcon(mTracker != null);
+ final boolean hasTrackerIcon = (mTracker != null);
+ if (mHasTrackerIcon != hasTrackerIcon) {
+ mHasTrackerIcon = hasTrackerIcon;
+ if (mNotificationProgressDrawable != null
+ && mNotificationProgressDrawable.getBounds().width() != 0
+ && mProgressModel.isStyledByProgress()) {
+ updateDrawableParts();
+ }
}
configureTrackerBounds();
@@ -293,6 +316,8 @@ public final class NotificationProgressBar extends ProgressBar {
mTrackerDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
}
+ // This updates the visual position of the progress indicator, i.e., the tracker. It doesn't
+ // update the NotificationProgressDrawable, which is updated by {@code #setProgressModel}.
@Override
public synchronized void setProgress(int progress) {
super.setProgress(progress);
@@ -300,6 +325,8 @@ public final class NotificationProgressBar extends ProgressBar {
onMaybeVisualProgressChanged();
}
+ // This updates the visual position of the progress indicator, i.e., the tracker. It doesn't
+ // update the NotificationProgressDrawable, which is updated by {@code #setProgressModel}.
@Override
public void setProgress(int progress, boolean animate) {
// Animation isn't supported by NotificationProgressBar.
@@ -308,6 +335,8 @@ public final class NotificationProgressBar extends ProgressBar {
onMaybeVisualProgressChanged();
}
+ // This updates the visual position of the progress indicator, i.e., the tracker. It doesn't
+ // update the NotificationProgressDrawable, which is updated by {@code #setProgressModel}.
@Override
public synchronized void setMin(int min) {
super.setMin(min);
@@ -315,6 +344,8 @@ public final class NotificationProgressBar extends ProgressBar {
onMaybeVisualProgressChanged();
}
+ // This updates the visual position of the progress indicator, i.e., the tracker. It doesn't
+ // update the NotificationProgressDrawable, which is updated by {@code #setProgressModel}.
@Override
public synchronized void setMax(int max) {
super.setMax(max);
@@ -323,10 +354,10 @@ public final class NotificationProgressBar extends ProgressBar {
}
private void onMaybeVisualProgressChanged() {
- float scale = getScale();
- if (mScale == scale) return;
+ float progressFraction = getProgressFraction();
+ if (mProgressFraction == progressFraction) return;
- mScale = scale;
+ mProgressFraction = progressFraction;
mTrackerPosIsDirty = true;
invalidate();
}
@@ -372,6 +403,59 @@ public final class NotificationProgressBar extends ProgressBar {
updateTrackerAndBarPos(w, h);
}
+ @Override
+ public void onDrawableBoundsChanged() {
+ final Rect progressDrawableBounds = mNotificationProgressDrawable.getBounds();
+
+ if (mProgressDrawableBounds.equals(progressDrawableBounds)) return;
+
+ if (mProgressDrawableBounds.width() != progressDrawableBounds.width()) {
+ updateDrawableParts();
+ }
+
+ mProgressDrawableBounds.set(progressDrawableBounds);
+ }
+
+ private void updateDrawableParts() {
+ Log.d(TAG, "updateDrawableParts() called. mNotificationProgressDrawable = "
+ + mNotificationProgressDrawable + ", mParts = " + mParts);
+
+ if (mNotificationProgressDrawable == null) return;
+ if (mParts == null) return;
+
+ final float width = mNotificationProgressDrawable.getBounds().width();
+ if (width == 0) {
+ if (mProgressDrawableParts != null) {
+ Log.d(TAG, "Clearing mProgressDrawableParts");
+ mProgressDrawableParts.clear();
+ mNotificationProgressDrawable.setParts(mProgressDrawableParts);
+ }
+ return;
+ }
+
+ mProgressDrawableParts = processAndConvertToDrawableParts(
+ mParts,
+ width,
+ mNotificationProgressDrawable.getSegSegGap(),
+ mNotificationProgressDrawable.getSegPointGap(),
+ mNotificationProgressDrawable.getPointRadius(),
+ mHasTrackerIcon
+ );
+ Pair<List<NotificationProgressDrawable.Part>, Float> p = maybeStretchAndRescaleSegments(
+ mParts,
+ mProgressDrawableParts,
+ mNotificationProgressDrawable.getSegmentMinWidth(),
+ mNotificationProgressDrawable.getPointRadius(),
+ getProgressFraction(),
+ width,
+ mProgressModel.isStyledByProgress(),
+ mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap());
+
+ Log.d(TAG, "Updating NotificationProgressDrawable parts");
+ mNotificationProgressDrawable.setParts(p.first);
+ mAdjustedProgressFraction = p.second / width;
+ }
+
private void updateTrackerAndBarPos(int w, int h) {
final int paddedHeight = h - mPaddingTop - mPaddingBottom;
final Drawable bar = getCurrentDrawable();
@@ -402,11 +486,11 @@ public final class NotificationProgressBar extends ProgressBar {
}
if (tracker != null) {
- setTrackerPos(w, tracker, mScale, trackerOffsetY);
+ setTrackerPos(w, tracker, mAdjustedProgressFraction, trackerOffsetY);
}
}
- private float getScale() {
+ private float getProgressFraction() {
int min = getMin();
int max = getMax();
int range = max - min;
@@ -418,17 +502,17 @@ public final class NotificationProgressBar extends ProgressBar {
*
* @param w Width of the view, including padding
* @param tracker Drawable used for the tracker
- * @param scale Current progress between 0 and 1
+ * @param progressFraction Current progress between 0 and 1
* @param offsetY Vertical offset for centering. If set to
* {@link Integer#MIN_VALUE}, the current offset will be used.
*/
- private void setTrackerPos(int w, Drawable tracker, float scale, int offsetY) {
+ private void setTrackerPos(int w, Drawable tracker, float progressFraction, int offsetY) {
int available = w - mPaddingLeft - mPaddingRight;
final int trackerWidth = tracker.getIntrinsicWidth();
final int trackerHeight = tracker.getIntrinsicHeight();
available -= ((mTrackerHeight <= 0) ? trackerWidth : mTrackerWidth);
- final int trackerPos = (int) (scale * available + 0.5f);
+ final int trackerPos = (int) (progressFraction * available + 0.5f);
final int top, bottom;
if (offsetY == Integer.MIN_VALUE) {
@@ -482,7 +566,7 @@ public final class NotificationProgressBar extends ProgressBar {
if (mTracker == null) return;
if (mTrackerPosIsDirty) {
- setTrackerPos(getWidth(), mTracker, mScale, Integer.MIN_VALUE);
+ setTrackerPos(getWidth(), mTracker, mAdjustedProgressFraction, Integer.MIN_VALUE);
}
final int saveCount = canvas.save();
@@ -531,7 +615,7 @@ public final class NotificationProgressBar extends ProgressBar {
final Drawable tracker = mTracker;
if (tracker != null) {
- setTrackerPos(getWidth(), tracker, mScale, Integer.MIN_VALUE);
+ setTrackerPos(getWidth(), tracker, mAdjustedProgressFraction, Integer.MIN_VALUE);
// Since we draw translated, the drawable's bounds that it signals
// for invalidation won't be the actual bounds we want invalidated,
@@ -541,16 +625,14 @@ public final class NotificationProgressBar extends ProgressBar {
}
/**
- * Processes the ProgressStyle data and convert to list of {@code
- * NotificationProgressDrawable.Part}.
+ * Processes the ProgressStyle data and convert to a list of {@code Part}.
*/
@VisibleForTesting
- public static List<Part> processAndConvertToDrawableParts(
+ public static List<Part> processAndConvertToViewParts(
List<ProgressStyle.Segment> segments,
List<ProgressStyle.Point> points,
int progress,
- int progressMax,
- boolean isStyledByProgress
+ int progressMax
) {
if (segments.isEmpty()) {
throw new IllegalArgumentException("List of segments shouldn't be empty");
@@ -571,6 +653,7 @@ public final class NotificationProgressBar extends ProgressBar {
if (progress < 0 || progress > progressMax) {
throw new IllegalArgumentException("Invalid progress : " + progress);
}
+
for (ProgressStyle.Point point : points) {
final int pos = point.getPosition();
if (pos < 0 || pos > progressMax) {
@@ -583,23 +666,21 @@ public final class NotificationProgressBar extends ProgressBar {
final Map<Integer, ProgressStyle.Point> positionToPointMap = generatePositionToPointMap(
points);
final SortedSet<Integer> sortedPos = generateSortedPositionSet(startToSegmentMap,
- positionToPointMap, progress, isStyledByProgress);
+ positionToPointMap);
final Map<Integer, ProgressStyle.Segment> startToSplitSegmentMap =
- splitSegmentsByPointsAndProgress(
- startToSegmentMap, sortedPos, progressMax);
+ splitSegmentsByPoints(startToSegmentMap, sortedPos, progressMax);
- return convertToDrawableParts(startToSplitSegmentMap, positionToPointMap, sortedPos,
- progress, progressMax,
- isStyledByProgress);
+ return convertToViewParts(startToSplitSegmentMap, positionToPointMap, sortedPos,
+ progressMax);
}
// Any segment with a point on it gets split by the point.
- // If isStyledByProgress is true, also split the segment with the progress value in its range.
- private static Map<Integer, ProgressStyle.Segment> splitSegmentsByPointsAndProgress(
+ private static Map<Integer, ProgressStyle.Segment> splitSegmentsByPoints(
Map<Integer, ProgressStyle.Segment> startToSegmentMap,
SortedSet<Integer> sortedPos,
- int progressMax) {
+ int progressMax
+ ) {
int prevSegStart = 0;
for (Integer pos : sortedPos) {
if (pos == 0 || pos == progressMax) continue;
@@ -624,32 +705,22 @@ public final class NotificationProgressBar extends ProgressBar {
return startToSegmentMap;
}
- private static List<Part> convertToDrawableParts(
+ private static List<Part> convertToViewParts(
Map<Integer, ProgressStyle.Segment> startToSegmentMap,
Map<Integer, ProgressStyle.Point> positionToPointMap,
SortedSet<Integer> sortedPos,
- int progress,
- int progressMax,
- boolean isStyledByProgress
+ int progressMax
) {
List<Part> parts = new ArrayList<>();
- boolean styleRemainingParts = false;
for (Integer pos : sortedPos) {
if (positionToPointMap.containsKey(pos)) {
final ProgressStyle.Point point = positionToPointMap.get(pos);
- final int color = maybeGetFadedColor(point.getColor(), styleRemainingParts);
- parts.add(new Point(null, color, styleRemainingParts));
- }
- // We want the Point at the current progress to be filled (not faded), but a Segment
- // starting at this progress to be faded.
- if (isStyledByProgress && !styleRemainingParts && pos == progress) {
- styleRemainingParts = true;
+ parts.add(new Point(point.getColor()));
}
if (startToSegmentMap.containsKey(pos)) {
final ProgressStyle.Segment seg = startToSegmentMap.get(pos);
- final int color = maybeGetFadedColor(seg.getColor(), styleRemainingParts);
parts.add(new Segment(
- (float) seg.getLength() / progressMax, color, styleRemainingParts));
+ (float) seg.getLength() / progressMax, seg.getColor()));
}
}
@@ -660,11 +731,24 @@ public final class NotificationProgressBar extends ProgressBar {
private static int maybeGetFadedColor(@ColorInt int color, boolean fade) {
if (!fade) return color;
- return NotificationProgressDrawable.getFadedColor(color);
+ return getFadedColor(color);
+ }
+
+ /**
+ * Get a color with an opacity that's 40% of the input color.
+ */
+ @ColorInt
+ static int getFadedColor(@ColorInt int color) {
+ return Color.argb(
+ (int) (Color.alpha(color) * 0.4f + 0.5f),
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color));
}
private static Map<Integer, ProgressStyle.Segment> generateStartToSegmentMap(
- List<ProgressStyle.Segment> segments) {
+ List<ProgressStyle.Segment> segments
+ ) {
final Map<Integer, ProgressStyle.Segment> startToSegmentMap = new HashMap<>();
int currentStart = 0; // Initial start position is 0
@@ -681,7 +765,8 @@ public final class NotificationProgressBar extends ProgressBar {
}
private static Map<Integer, ProgressStyle.Point> generatePositionToPointMap(
- List<ProgressStyle.Point> points) {
+ List<ProgressStyle.Point> points
+ ) {
final Map<Integer, ProgressStyle.Point> positionToPointMap = new HashMap<>();
for (ProgressStyle.Point point : points) {
@@ -693,14 +778,404 @@ public final class NotificationProgressBar extends ProgressBar {
private static SortedSet<Integer> generateSortedPositionSet(
Map<Integer, ProgressStyle.Segment> startToSegmentMap,
- Map<Integer, ProgressStyle.Point> positionToPointMap, int progress,
- boolean isStyledByProgress) {
+ Map<Integer, ProgressStyle.Point> positionToPointMap
+ ) {
final SortedSet<Integer> sortedPos = new TreeSet<>(startToSegmentMap.keySet());
sortedPos.addAll(positionToPointMap.keySet());
- if (isStyledByProgress) {
- sortedPos.add(progress);
- }
return sortedPos;
}
+
+ /**
+ * Processes the list of {@code Part} and convert to a list of
+ * {@code NotificationProgressDrawable.Part}.
+ */
+ @VisibleForTesting
+ public static List<NotificationProgressDrawable.Part> processAndConvertToDrawableParts(
+ List<Part> parts,
+ float totalWidth,
+ float segSegGap,
+ float segPointGap,
+ float pointRadius,
+ boolean hasTrackerIcon
+ ) {
+ List<NotificationProgressDrawable.Part> drawableParts = new ArrayList<>();
+
+ // generally, we will start drawing at (x, y) and end at (x+w, y)
+ float x = (float) 0;
+
+ final int nParts = parts.size();
+ for (int iPart = 0; iPart < nParts; iPart++) {
+ final Part part = parts.get(iPart);
+ final Part prevPart = iPart == 0 ? null : parts.get(iPart - 1);
+ final Part nextPart = iPart + 1 == nParts ? null : parts.get(iPart + 1);
+ if (part instanceof Segment segment) {
+ final float segWidth = segment.mFraction * totalWidth;
+ // Advance the start position to account for a point immediately prior.
+ final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x);
+ final float start = x + startOffset;
+ // Retract the end position to account for the padding and a point immediately
+ // after.
+ final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
+ segSegGap, x + segWidth, totalWidth, hasTrackerIcon);
+ final float end = x + segWidth - endOffset;
+
+ drawableParts.add(
+ new NotificationProgressDrawable.Segment(start, end, segment.mColor,
+ segment.mFaded));
+
+ segment.mStart = x;
+ segment.mEnd = x + segWidth;
+
+ // Advance the current position to account for the segment's fraction of the total
+ // width (ignoring offset and padding)
+ x += segWidth;
+ } else if (part instanceof Point point) {
+ final float pointWidth = 2 * pointRadius;
+ float start = x - pointRadius;
+ if (start < 0) start = 0;
+ float end = start + pointWidth;
+ if (end > totalWidth) {
+ end = totalWidth;
+ if (totalWidth > pointWidth) start = totalWidth - pointWidth;
+ }
+
+ drawableParts.add(
+ new NotificationProgressDrawable.Point(start, end, point.mColor));
+ }
+ }
+
+ return drawableParts;
+ }
+
+ private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap,
+ float startX) {
+ if (!(prevPart instanceof Point)) return 0F;
+ final float pointOffset = (startX < pointRadius) ? (pointRadius - startX) : 0;
+ return pointOffset + pointRadius + segPointGap;
+ }
+
+ private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
+ float segPointGap,
+ float segSegGap, float endX, float totalWidth, boolean hasTrackerIcon) {
+ if (nextPart == null) return 0F;
+ if (nextPart instanceof Segment nextSeg) {
+ if (!seg.mFaded && nextSeg.mFaded) {
+ // @see Segment#mFaded
+ return hasTrackerIcon ? 0F : segSegGap;
+ }
+ return segSegGap;
+ }
+
+ final float pointWidth = 2 * pointRadius;
+ final float pointOffset = (endX + pointRadius > totalWidth && totalWidth > pointWidth)
+ ? (endX + pointRadius - totalWidth) : 0;
+ return segPointGap + pointRadius + pointOffset;
+ }
+
+ /**
+ * Processes the list of {@code NotificationProgressBar.Part} data and convert to a pair of:
+ * - list of {@code NotificationProgressDrawable.Part}.
+ * - location of progress on the stretched and rescaled progress bar.
+ */
+ @VisibleForTesting
+ public static Pair<List<NotificationProgressDrawable.Part>, Float>
+ maybeStretchAndRescaleSegments(
+ List<Part> parts,
+ List<NotificationProgressDrawable.Part> drawableParts,
+ float segmentMinWidth,
+ float pointRadius,
+ float progressFraction,
+ float totalWidth,
+ boolean isStyledByProgress,
+ float progressGap
+ ) {
+ final List<NotificationProgressDrawable.Segment> drawableSegments = drawableParts
+ .stream()
+ .filter(NotificationProgressDrawable.Segment.class::isInstance)
+ .map(NotificationProgressDrawable.Segment.class::cast)
+ .toList();
+ float totalExcessWidth = 0;
+ float totalPositiveExcessWidth = 0;
+ for (NotificationProgressDrawable.Segment drawableSegment : drawableSegments) {
+ final float excessWidth = drawableSegment.getWidth() - segmentMinWidth;
+ totalExcessWidth += excessWidth;
+ if (excessWidth > 0) totalPositiveExcessWidth += excessWidth;
+ }
+
+ // All drawable segments are above minimum width. No need to stretch and rescale.
+ if (totalExcessWidth == totalPositiveExcessWidth) {
+ return maybeSplitDrawableSegmentsByProgress(
+ parts,
+ drawableParts,
+ progressFraction,
+ totalWidth,
+ isStyledByProgress,
+ progressGap);
+ }
+
+ if (totalExcessWidth < 0) {
+ // TODO: b/372908709 - throw an error so that the caller can catch and go to fallback
+ // option. (instead of return.)
+ Log.w(TAG, "Not enough width to satisfy the minimum width for segments.");
+ return maybeSplitDrawableSegmentsByProgress(
+ parts,
+ drawableParts,
+ progressFraction,
+ totalWidth,
+ isStyledByProgress,
+ progressGap);
+ }
+
+ final int nParts = drawableParts.size();
+ float startOffset = 0;
+ for (int iPart = 0; iPart < nParts; iPart++) {
+ final NotificationProgressDrawable.Part drawablePart = drawableParts.get(iPart);
+ if (drawablePart instanceof NotificationProgressDrawable.Segment drawableSegment) {
+ final float origDrawableSegmentWidth = drawableSegment.getWidth();
+
+ float drawableSegmentWidth = segmentMinWidth;
+ // Allocate the totalExcessWidth to the segments above minimum, proportionally to
+ // their initial excessWidth.
+ if (origDrawableSegmentWidth > segmentMinWidth) {
+ drawableSegmentWidth +=
+ totalExcessWidth * (origDrawableSegmentWidth - segmentMinWidth)
+ / totalPositiveExcessWidth;
+ }
+
+ final float widthDiff = drawableSegmentWidth - drawableSegment.getWidth();
+
+ // Adjust drawable segments to new widths
+ drawableSegment.setStart(drawableSegment.getStart() + startOffset);
+ drawableSegment.setEnd(
+ drawableSegment.getStart() + origDrawableSegmentWidth + widthDiff);
+
+ // Also adjust view segments to new width. (For view segments, only start is
+ // needed?)
+ // Check that segments and drawableSegments are of the same size?
+ final Segment segment = (Segment) parts.get(iPart);
+ final float origSegmentWidth = segment.getWidth();
+ segment.mStart = segment.mStart + startOffset;
+ segment.mEnd = segment.mStart + origSegmentWidth + widthDiff;
+
+ // Increase startOffset for the subsequent segments.
+ startOffset += widthDiff;
+ } else if (drawablePart instanceof NotificationProgressDrawable.Point drawablePoint) {
+ drawablePoint.setStart(drawablePoint.getStart() + startOffset);
+ drawablePoint.setEnd(drawablePoint.getStart() + 2 * pointRadius);
+ }
+ }
+
+ return maybeSplitDrawableSegmentsByProgress(
+ parts,
+ drawableParts,
+ progressFraction,
+ totalWidth,
+ isStyledByProgress,
+ progressGap);
+ }
+
+ // Find the location of progress on the stretched and rescaled progress bar.
+ // If isStyledByProgress is true, also split the segment with the progress value in its range.
+ private static Pair<List<NotificationProgressDrawable.Part>, Float>
+ maybeSplitDrawableSegmentsByProgress(
+ // Needed to get the original segment start and end positions in pixels.
+ List<Part> parts,
+ List<NotificationProgressDrawable.Part> drawableParts,
+ float progressFraction,
+ float totalWidth,
+ boolean isStyledByProgress,
+ float progressGap
+ ) {
+ if (progressFraction == 1) return new Pair<>(drawableParts, totalWidth);
+
+ int iPartFirstSegmentToStyle = -1;
+ int iPartSegmentToSplit = -1;
+ float rescaledProgressX = 0;
+ float startFraction = 0;
+ final int nParts = parts.size();
+ for (int iPart = 0; iPart < nParts; iPart++) {
+ final Part part = parts.get(iPart);
+ if (!(part instanceof Segment)) continue;
+ final Segment segment = (Segment) part;
+ if (startFraction == progressFraction) {
+ iPartFirstSegmentToStyle = iPart;
+ rescaledProgressX = segment.mStart;
+ break;
+ } else if (startFraction < progressFraction
+ && progressFraction < startFraction + segment.mFraction) {
+ iPartSegmentToSplit = iPart;
+ rescaledProgressX =
+ segment.mStart + (progressFraction - startFraction) / segment.mFraction
+ * segment.getWidth();
+ break;
+ }
+ startFraction += segment.mFraction;
+ }
+
+ if (!isStyledByProgress) return new Pair<>(drawableParts, rescaledProgressX);
+
+ List<NotificationProgressDrawable.Part> splitDrawableParts = new ArrayList<>();
+ boolean styleRemainingParts = false;
+ for (int iPart = 0; iPart < nParts; iPart++) {
+ final NotificationProgressDrawable.Part drawablePart = drawableParts.get(iPart);
+ if (drawablePart instanceof NotificationProgressDrawable.Point drawablePoint) {
+ final int color = maybeGetFadedColor(drawablePoint.getColor(), styleRemainingParts);
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Point(drawablePoint.getStart(),
+ drawablePoint.getEnd(), color));
+ }
+ if (iPart == iPartFirstSegmentToStyle) styleRemainingParts = true;
+ if (drawablePart instanceof NotificationProgressDrawable.Segment drawableSegment) {
+ if (iPart == iPartSegmentToSplit) {
+ if (rescaledProgressX <= drawableSegment.getStart()) {
+ styleRemainingParts = true;
+ final int color = maybeGetFadedColor(drawableSegment.getColor(), true);
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(drawableSegment.getStart(),
+ drawableSegment.getEnd(),
+ color, true));
+ } else if (drawableSegment.getStart() < rescaledProgressX
+ && rescaledProgressX < drawableSegment.getEnd()) {
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(drawableSegment.getStart(),
+ rescaledProgressX - progressGap,
+ drawableSegment.getColor()));
+ final int color = maybeGetFadedColor(drawableSegment.getColor(), true);
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(rescaledProgressX,
+ drawableSegment.getEnd(), color, true));
+ styleRemainingParts = true;
+ } else {
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(drawableSegment.getStart(),
+ drawableSegment.getEnd(),
+ drawableSegment.getColor()));
+ styleRemainingParts = true;
+ }
+ } else {
+ final int color = maybeGetFadedColor(drawableSegment.getColor(),
+ styleRemainingParts);
+ splitDrawableParts.add(
+ new NotificationProgressDrawable.Segment(drawableSegment.getStart(),
+ drawableSegment.getEnd(),
+ color, styleRemainingParts));
+ }
+ }
+ }
+
+ return new Pair<>(splitDrawableParts, rescaledProgressX);
+ }
+
+ /**
+ * A part of the progress bar, which is either a {@link Segment} with non-zero length, or a
+ * {@link Point} with zero length.
+ */
+ // TODO: b/372908709 - maybe this should be made private? Only test the final
+ // NotificationDrawable.Parts.
+ // TODO: b/372908709 - rename to BarPart, BarSegment, BarPoint. This avoids naming ambiguity
+ // with the types in NotificationProgressDrawable.
+ public interface Part {
+ }
+
+ /**
+ * A segment is a part of the progress bar with non-zero length. For example, it can
+ * represent a portion in a navigation journey with certain traffic condition.
+ *
+ */
+ public static final class Segment implements Part {
+ private final float mFraction;
+ @ColorInt private final int mColor;
+ /** Whether the segment is faded or not.
+ * <p>
+ * <pre>
+ * When mFaded is set to true, a combination of the following is done to the segment:
+ * 1. The drawing color is mColor with opacity updated to 40%.
+ * 2. The gap between faded and non-faded segments is:
+ * - the segment-segment gap, when there is no tracker icon
+ * - 0, when there is tracker icon
+ * </pre>
+ * </p>
+ */
+ private final boolean mFaded;
+
+ /** Start position (in pixels) */
+ private float mStart;
+ /** End position (in pixels */
+ private float mEnd;
+
+ public Segment(float fraction, @ColorInt int color) {
+ this(fraction, color, false);
+ }
+
+ public Segment(float fraction, @ColorInt int color, boolean faded) {
+ mFraction = fraction;
+ mColor = color;
+ mFaded = faded;
+ }
+
+ /** Returns the calculated drawing width of the part */
+ public float getWidth() {
+ return mEnd - mStart;
+ }
+
+ @Override
+ public String toString() {
+ return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", faded="
+ + this.mFaded + "), mStart = " + this.mStart + ", mEnd = " + this.mEnd;
+ }
+
+ // Needed for unit tests
+ @Override
+ public boolean equals(@androidx.annotation.Nullable Object other) {
+ if (this == other) return true;
+
+ if (other == null || getClass() != other.getClass()) return false;
+
+ Segment that = (Segment) other;
+ if (Float.compare(this.mFraction, that.mFraction) != 0) return false;
+ if (this.mColor != that.mColor) return false;
+ return this.mFaded == that.mFaded;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFraction, mColor, mFaded);
+ }
+ }
+
+ /**
+ * A point is a part of the progress bar with zero length. Points are designated points within a
+ * progress bar to visualize distinct stages or milestones. For example, a stop in a multi-stop
+ * ride-share journey.
+ */
+ public static final class Point implements Part {
+ @ColorInt private final int mColor;
+
+ public Point(@ColorInt int color) {
+ mColor = color;
+ }
+
+ @Override
+ public String toString() {
+ return "Point(color=" + this.mColor + ")";
+ }
+
+ // Needed for unit tests.
+ @Override
+ public boolean equals(@androidx.annotation.Nullable Object other) {
+ if (this == other) return true;
+
+ if (other == null || getClass() != other.getClass()) return false;
+
+ Point that = (Point) other;
+
+ return this.mColor == that.mColor;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mColor);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index 8629a1c95202..ef0a5d5cdec2 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -21,7 +21,6 @@ import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -49,7 +48,8 @@ import java.util.Objects;
/**
* This is used by NotificationProgressBar for displaying a custom background. It composes of
- * segments, which have non-zero length, and points, which have zero length.
+ * segments, which have non-zero length varying drawing width, and points, which have zero length
+ * and fixed size for drawing.
*
* @see Segment
* @see Point
@@ -57,14 +57,15 @@ import java.util.Objects;
public final class NotificationProgressDrawable extends Drawable {
private static final String TAG = "NotifProgressDrawable";
+ @Nullable
+ private BoundsChangeListener mBoundsChangeListener = null;
+
private State mState;
private boolean mMutated;
private final ArrayList<Part> mParts = new ArrayList<>();
- private boolean mHasTrackerIcon;
private final RectF mSegRectF = new RectF();
- private final Rect mPointRect = new Rect();
private final RectF mPointRectF = new RectF();
private final Paint mFillPaint = new Paint();
@@ -80,27 +81,31 @@ public final class NotificationProgressDrawable extends Drawable {
}
/**
- * <p>Set the segment default color for the drawable.</p>
- * <p>Note: changing this property will affect all instances of a drawable loaded from a
- * resource. It is recommended to invoke {@link #mutate()} before changing this property.</p>
- *
- * @param color The color of the stroke
- * @see #mutate()
+ * Returns the gap between two segments.
*/
- public void setSegmentDefaultColor(@ColorInt int color) {
- mState.setSegmentColor(color);
+ public float getSegSegGap() {
+ return mState.mSegSegGap;
}
/**
- * <p>Set the point rect default color for the drawable.</p>
- * <p>Note: changing this property will affect all instances of a drawable loaded from a
- * resource. It is recommended to invoke {@link #mutate()} before changing this property.</p>
- *
- * @param color The color of the point rect
- * @see #mutate()
+ * Returns the gap between a segment and a point.
+ */
+ public float getSegPointGap() {
+ return mState.mSegPointGap;
+ }
+
+ /**
+ * Returns the gap between a segment and a point.
*/
- public void setPointRectDefaultColor(@ColorInt int color) {
- mState.setPointRectColor(color);
+ public float getSegmentMinWidth() {
+ return mState.mSegmentMinWidth;
+ }
+
+ /**
+ * Returns the radius for the points.
+ */
+ public float getPointRadius() {
+ return mState.mPointRadius;
}
/**
@@ -120,47 +125,18 @@ public final class NotificationProgressDrawable extends Drawable {
setParts(Arrays.asList(parts));
}
- /**
- * Set whether a tracker is drawn on top of this NotificationProgressDrawable.
- */
- public void setHasTrackerIcon(boolean hasTrackerIcon) {
- if (mHasTrackerIcon != hasTrackerIcon) {
- mHasTrackerIcon = hasTrackerIcon;
- invalidateSelf();
- }
- }
-
@Override
public void draw(@NonNull Canvas canvas) {
- final float pointRadius =
- mState.mPointRadius; // how big the point icon will be, halved
-
- // generally, we will start drawing at (x, y) and end at (x+w, y)
- float x = (float) getBounds().left;
+ final float pointRadius = mState.mPointRadius;
+ final float left = (float) getBounds().left;
final float centerY = (float) getBounds().centerY();
- final float totalWidth = (float) getBounds().width();
- float segPointGap = mState.mSegPointGap;
final int numParts = mParts.size();
for (int iPart = 0; iPart < numParts; iPart++) {
final Part part = mParts.get(iPart);
- final Part prevPart = iPart == 0 ? null : mParts.get(iPart - 1);
- final Part nextPart = iPart + 1 == numParts ? null : mParts.get(iPart + 1);
+ final float start = left + part.mStart;
+ final float end = left + part.mEnd;
if (part instanceof Segment segment) {
- final float segWidth = segment.mFraction * totalWidth;
- // Advance the start position to account for a point immediately prior.
- final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x);
- final float start = x + startOffset;
- // Retract the end position to account for the padding and a point immediately
- // after.
- final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
- mState.mSegSegGap, x + segWidth, totalWidth, mHasTrackerIcon);
- final float end = x + segWidth - endOffset;
-
- // Advance the current position to account for the segment's fraction of the total
- // width (ignoring offset and padding)
- x += segWidth;
-
// No space left to draw the segment
if (start > end) continue;
@@ -168,67 +144,23 @@ public final class NotificationProgressDrawable extends Drawable {
: mState.mSegmentHeight / 2F;
final float cornerRadius = mState.mSegmentCornerRadius;
- mFillPaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
- : (segment.mFaded ? mState.mFadedSegmentColor : mState.mSegmentColor));
+ mFillPaint.setColor(segment.mColor);
mSegRectF.set(start, centerY - radiusY, end, centerY + radiusY);
canvas.drawRoundRect(mSegRectF, cornerRadius, cornerRadius, mFillPaint);
} else if (part instanceof Point point) {
- final float pointWidth = 2 * pointRadius;
- float start = x - pointRadius;
- if (start < 0) start = 0;
- float end = start + pointWidth;
- if (end > totalWidth) {
- end = totalWidth;
- if (totalWidth > pointWidth) start = totalWidth - pointWidth;
- }
- mPointRect.set((int) start, (int) (centerY - pointRadius), (int) end,
- (int) (centerY + pointRadius));
-
- if (point.mIcon != null) {
- point.mIcon.setBounds(mPointRect);
- point.mIcon.draw(canvas);
- } else {
- // TODO: b/367804171 - actually use a vector asset for the default point
- // rather than drawing it as a box?
- mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius);
- final float inset = mState.mPointRectInset;
- final float cornerRadius = mState.mPointRectCornerRadius;
- mPointRectF.inset(inset, inset);
-
- mFillPaint.setColor(point.mColor != Color.TRANSPARENT ? point.mColor
- : (point.mFaded ? mState.mFadedPointRectColor
- : mState.mPointRectColor));
-
- canvas.drawRoundRect(mPointRectF, cornerRadius, cornerRadius, mFillPaint);
- }
- }
- }
- }
+ // TODO: b/367804171 - actually use a vector asset for the default point
+ // rather than drawing it as a box?
+ mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius);
+ final float inset = mState.mPointRectInset;
+ final float cornerRadius = mState.mPointRectCornerRadius;
+ mPointRectF.inset(inset, inset);
- private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap,
- float startX) {
- if (!(prevPart instanceof Point)) return 0F;
- final float pointOffset = (startX < pointRadius) ? (pointRadius - startX) : 0;
- return pointOffset + pointRadius + segPointGap;
- }
+ mFillPaint.setColor(point.mColor);
- private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
- float segPointGap,
- float segSegGap, float endX, float totalWidth, boolean hasTrackerIcon) {
- if (nextPart == null) return 0F;
- if (nextPart instanceof Segment nextSeg) {
- if (!seg.mFaded && nextSeg.mFaded) {
- // @see Segment#mFaded
- return hasTrackerIcon ? 0F : segSegGap;
+ canvas.drawRoundRect(mPointRectF, cornerRadius, cornerRadius, mFillPaint);
}
- return segSegGap;
}
-
- final float pointWidth = 2 * pointRadius;
- final float pointOffset = (endX + pointRadius > totalWidth && totalWidth > pointWidth)
- ? (endX + pointRadius - totalWidth) : 0;
- return segPointGap + pointRadius + pointOffset;
}
@Override
@@ -260,6 +192,19 @@ public final class NotificationProgressDrawable extends Drawable {
return PixelFormat.UNKNOWN;
}
+ public void setBoundsChangeListener(BoundsChangeListener listener) {
+ mBoundsChangeListener = listener;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+
+ if (mBoundsChangeListener != null) {
+ mBoundsChangeListener.onDrawableBoundsChanged();
+ }
+ }
+
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
@@ -384,6 +329,8 @@ public final class NotificationProgressDrawable extends Drawable {
// Extract the theme attributes, if any.
state.mThemeAttrsSegments = a.extractThemeAttrs();
+ state.mSegmentMinWidth = a.getDimension(
+ R.styleable.NotificationProgressDrawableSegments_minWidth, state.mSegmentMinWidth);
state.mSegmentHeight = a.getDimension(
R.styleable.NotificationProgressDrawableSegments_height, state.mSegmentHeight);
state.mFadedSegmentHeight = a.getDimension(
@@ -392,9 +339,6 @@ public final class NotificationProgressDrawable extends Drawable {
state.mSegmentCornerRadius = a.getDimension(
R.styleable.NotificationProgressDrawableSegments_cornerRadius,
state.mSegmentCornerRadius);
- final int color = a.getColor(R.styleable.NotificationProgressDrawableSegments_color,
- state.mSegmentColor);
- setSegmentDefaultColor(color);
}
private void updatePointsFromTypedArray(TypedArray a) {
@@ -413,9 +357,6 @@ public final class NotificationProgressDrawable extends Drawable {
state.mPointRectCornerRadius = a.getDimension(
R.styleable.NotificationProgressDrawablePoints_cornerRadius,
state.mPointRectCornerRadius);
- final int color = a.getColor(R.styleable.NotificationProgressDrawablePoints_color,
- state.mPointRectColor);
- setPointRectDefaultColor(color);
}
static int resolveDensity(@Nullable Resources r, int parentDensity) {
@@ -464,63 +405,56 @@ public final class NotificationProgressDrawable extends Drawable {
}
/**
- * A part of the progress bar, which is either a S{@link Segment} with non-zero length, or a
- * {@link Point} with zero length.
+ * Listener to receive updates about drawable bounds changing
*/
- public interface Part {
+ public interface BoundsChangeListener {
+ /** Called when bounds have changed */
+ void onDrawableBoundsChanged();
}
/**
- * A segment is a part of the progress bar with non-zero length. For example, it can
- * represent a portion in a navigation journey with certain traffic condition.
- *
+ * A part of the progress bar, which is either a {@link Segment} with non-zero length and
+ * varying drawing width, or a {@link Point} with zero length and fixed size for drawing.
*/
- public static final class Segment implements Part {
- private final float mFraction;
- @ColorInt private final int mColor;
- /** Whether the segment is faded or not.
- * <p>
- * <pre>
- * When mFaded is set to true, a combination of the following is done to the segment:
- * 1. The drawing color is mColor with opacity updated to 40%.
- * 2. The gap between faded and non-faded segments is:
- * - the segment-segment gap, when there is no tracker icon
- * - 0, when there is tracker icon
- * </pre>
- * </p>
- */
- private final boolean mFaded;
-
- public Segment(float fraction) {
- this(fraction, Color.TRANSPARENT);
+ public abstract static class Part {
+ // TODO: b/372908709 - maybe rename start/end to left/right, to be consistent with the
+ // bounds rect.
+ /** Start position for drawing (in pixels) */
+ protected float mStart;
+ /** End position for drawing (in pixels) */
+ protected float mEnd;
+ /** Drawing color. */
+ @ColorInt protected final int mColor;
+
+ protected Part(float start, float end, @ColorInt int color) {
+ mStart = start;
+ mEnd = end;
+ mColor = color;
}
- public Segment(float fraction, @ColorInt int color) {
- this(fraction, color, false);
+ public float getStart() {
+ return this.mStart;
}
- public Segment(float fraction, @ColorInt int color, boolean faded) {
- mFraction = fraction;
- mColor = color;
- mFaded = faded;
+ public void setStart(float start) {
+ mStart = start;
}
- public float getFraction() {
- return this.mFraction;
+ public float getEnd() {
+ return this.mEnd;
}
- public int getColor() {
- return this.mColor;
+ public void setEnd(float end) {
+ mEnd = end;
}
- public boolean getFaded() {
- return this.mFaded;
+ /** Returns the calculated drawing width of the part */
+ public float getWidth() {
+ return mEnd - mStart;
}
- @Override
- public String toString() {
- return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", faded="
- + this.mFaded + ')';
+ public int getColor() {
+ return this.mColor;
}
// Needed for unit tests
@@ -530,80 +464,79 @@ public final class NotificationProgressDrawable extends Drawable {
if (other == null || getClass() != other.getClass()) return false;
- Segment that = (Segment) other;
- if (Float.compare(this.mFraction, that.mFraction) != 0) return false;
- if (this.mColor != that.mColor) return false;
- return this.mFaded == that.mFaded;
+ Part that = (Part) other;
+ if (Float.compare(this.mStart, that.mStart) != 0) return false;
+ if (Float.compare(this.mEnd, that.mEnd) != 0) return false;
+ return this.mColor == that.mColor;
}
@Override
public int hashCode() {
- return Objects.hash(mFraction, mColor, mFaded);
+ return Objects.hash(mStart, mEnd, mColor);
}
}
/**
- * A point is a part of the progress bar with zero length. Points are designated points within a
- * progressbar to visualize distinct stages or milestones. For example, a stop in a multi-stop
- * ride-share journey.
+ * A segment is a part of the progress bar with non-zero length. For example, it can
+ * represent a portion in a navigation journey with certain traffic condition.
+ * <p>
+ * The start and end positions for drawing a segment are assumed to have been adjusted for
+ * the Points and gaps neighboring the segment.
+ * </p>
*/
- public static final class Point implements Part {
- @Nullable
- private final Drawable mIcon;
- @ColorInt private final int mColor;
+ public static final class Segment extends Part {
+ /**
+ * Whether the segment is faded or not.
+ * <p>
+ * Faded segments and non-faded segments are drawn with different heights.
+ * </p>
+ */
private final boolean mFaded;
- public Point(@Nullable Drawable icon) {
- this(icon, Color.TRANSPARENT, false);
- }
-
- public Point(@Nullable Drawable icon, @ColorInt int color) {
- this(icon, color, false);
-
+ public Segment(float start, float end, int color) {
+ this(start, end, color, false);
}
- public Point(@Nullable Drawable icon, @ColorInt int color, boolean faded) {
- mIcon = icon;
- mColor = color;
+ public Segment(float start, float end, int color, boolean faded) {
+ super(start, end, color);
mFaded = faded;
}
- @Nullable
- public Drawable getIcon() {
- return this.mIcon;
- }
-
- public int getColor() {
- return this.mColor;
- }
-
- public boolean getFaded() {
- return this.mFaded;
- }
-
@Override
public String toString() {
- return "Point(icon=" + this.mIcon + ", color=" + this.mColor + ", faded=" + this.mFaded
- + ")";
+ return "Segment(start=" + this.mStart + ", end=" + this.mEnd + ", color=" + this.mColor
+ + ", faded=" + this.mFaded + ')';
}
// Needed for unit tests.
@Override
public boolean equals(@Nullable Object other) {
- if (this == other) return true;
-
- if (other == null || getClass() != other.getClass()) return false;
-
- Point that = (Point) other;
+ if (!super.equals(other)) return false;
- if (!Objects.equals(this.mIcon, that.mIcon)) return false;
- if (this.mColor != that.mColor) return false;
+ Segment that = (Segment) other;
return this.mFaded == that.mFaded;
}
@Override
public int hashCode() {
- return Objects.hash(mIcon, mColor, mFaded);
+ return Objects.hash(super.hashCode(), mFaded);
+ }
+ }
+
+ /**
+ * A point is a part of the progress bar with zero length. Points are designated points within a
+ * progress bar to visualize distinct stages or milestones. For example, a stop in a multi-stop
+ * ride-share journey.
+ */
+ public static final class Point extends Part {
+ public Point(float start, float end, int color) {
+ super(start, end, color);
+ }
+
+ @Override
+ public String toString() {
+ return "Point(start=" + this.mStart + ", end=" + this.mEnd + ", color=" + this.mColor
+ + ")";
}
}
@@ -628,16 +561,14 @@ public final class NotificationProgressDrawable extends Drawable {
int mChangingConfigurations;
float mSegSegGap = 0.0f;
float mSegPointGap = 0.0f;
+ float mSegmentMinWidth = 0.0f;
float mSegmentHeight;
float mFadedSegmentHeight;
float mSegmentCornerRadius;
- int mSegmentColor;
- int mFadedSegmentColor;
+ // how big the point icon will be, halved
float mPointRadius;
float mPointRectInset;
float mPointRectCornerRadius;
- int mPointRectColor;
- int mFadedPointRectColor;
int[] mThemeAttrs;
int[] mThemeAttrsSegments;
@@ -652,16 +583,13 @@ public final class NotificationProgressDrawable extends Drawable {
mChangingConfigurations = orig.mChangingConfigurations;
mSegSegGap = orig.mSegSegGap;
mSegPointGap = orig.mSegPointGap;
+ mSegmentMinWidth = orig.mSegmentMinWidth;
mSegmentHeight = orig.mSegmentHeight;
mFadedSegmentHeight = orig.mFadedSegmentHeight;
mSegmentCornerRadius = orig.mSegmentCornerRadius;
- mSegmentColor = orig.mSegmentColor;
- mFadedSegmentColor = orig.mFadedSegmentColor;
mPointRadius = orig.mPointRadius;
mPointRectInset = orig.mPointRectInset;
mPointRectCornerRadius = orig.mPointRectCornerRadius;
- mPointRectColor = orig.mPointRectColor;
- mFadedPointRectColor = orig.mFadedPointRectColor;
mThemeAttrs = orig.mThemeAttrs;
mThemeAttrsSegments = orig.mThemeAttrsSegments;
@@ -674,6 +602,18 @@ public final class NotificationProgressDrawable extends Drawable {
}
private void applyDensityScaling(int sourceDensity, int targetDensity) {
+ if (mSegSegGap > 0) {
+ mSegSegGap = scaleFromDensity(
+ mSegSegGap, sourceDensity, targetDensity);
+ }
+ if (mSegPointGap > 0) {
+ mSegPointGap = scaleFromDensity(
+ mSegPointGap, sourceDensity, targetDensity);
+ }
+ if (mSegmentMinWidth > 0) {
+ mSegmentMinWidth = scaleFromDensity(
+ mSegmentMinWidth, sourceDensity, targetDensity);
+ }
if (mSegmentHeight > 0) {
mSegmentHeight = scaleFromDensity(
mSegmentHeight, sourceDensity, targetDensity);
@@ -740,28 +680,6 @@ public final class NotificationProgressDrawable extends Drawable {
applyDensityScaling(sourceDensity, targetDensity);
}
}
-
- public void setSegmentColor(int color) {
- mSegmentColor = color;
- mFadedSegmentColor = getFadedColor(color);
- }
-
- public void setPointRectColor(int color) {
- mPointRectColor = color;
- mFadedPointRectColor = getFadedColor(color);
- }
- }
-
- /**
- * Get a color with an opacity that's 25% of the input color.
- */
- @ColorInt
- static int getFadedColor(@ColorInt int color) {
- return Color.argb(
- (int) (Color.alpha(color) * 0.4f + 0.5f),
- Color.red(color),
- Color.green(color),
- Color.blue(color));
}
@Override
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 96d34a0230b3..4f7ba9388a1d 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -107,6 +107,8 @@ message SecureSettingsProto {
optional SettingProto accessibility_key_gesture_targets = 59 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto hct_rect_prompt_status = 60 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto em_value = 61 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // Settings for accessibility autoclick
+ optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml
index 5d272fb00e34..ff5450ee106f 100644
--- a/core/res/res/drawable/notification_progress.xml
+++ b/core/res/res/drawable/notification_progress.xml
@@ -24,6 +24,7 @@
android:segPointGap="@dimen/notification_progress_segPoint_gap">
<segments
android:color="?attr/colorProgressBackgroundNormal"
+ android:minWidth="@dimen/notification_progress_segments_min_width"
android:height="@dimen/notification_progress_segments_height"
android:fadedHeight="@dimen/notification_progress_segments_faded_height"
android:cornerRadius="@dimen/notification_progress_segments_corner_radius"/>
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index db79e79c96df..1bde17358825 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -136,10 +136,10 @@
<ImageView
android:id="@+id/phishing_alert"
- android:layout_width="@dimen/notification_phishing_alert_size"
- android:layout_height="@dimen/notification_phishing_alert_size"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:baseline="10dp"
+ 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"
@@ -148,10 +148,10 @@
<ImageView
android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:baseline="10dp"
+ 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"
@@ -159,10 +159,10 @@
<ImageView
android:id="@+id/alerted_icon"
- android:layout_width="@dimen/notification_alerted_size"
- android:layout_height="@dimen/notification_alerted_size"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:baseline="10dp"
+ 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"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index f108ce5bd1b9..d29b7af9e24e 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -87,7 +87,7 @@
>
<!--
- NOTE: The notification_top_line_views layout contains the app_name_text.
+ 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.
-->
@@ -104,7 +104,7 @@
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
/>
- <include layout="@layout/notification_top_line_views" />
+ <include layout="@layout/notification_2025_top_line_views" />
</NotificationTopLineView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index bd17a3a0a74e..5beab508aecf 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -89,7 +89,7 @@
>
<!--
- NOTE: The notification_top_line_views layout contains the app_name_text.
+ 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.
-->
@@ -106,7 +106,7 @@
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
/>
- <include layout="@layout/notification_top_line_views" />
+ <include layout="@layout/notification_2025_top_line_views" />
</NotificationTopLineView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index edbebb17f825..d7c3263904d4 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -115,7 +115,7 @@
>
<!--
- NOTE: The notification_top_line_views layout contains the app_name_text.
+ 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.
-->
@@ -132,7 +132,7 @@
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
/>
- <include layout="@layout/notification_top_line_views" />
+ <include layout="@layout/notification_2025_top_line_views" />
</NotificationTopLineView>
diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml
index 0c07053d428a..72b3798e0780 100644
--- a/core/res/res/layout/notification_2025_template_header.xml
+++ b/core/res/res/layout/notification_2025_template_header.xml
@@ -68,7 +68,7 @@
android:theme="@style/Theme.DeviceDefault.Notification"
>
- <include layout="@layout/notification_top_line_views" />
+ <include layout="@layout/notification_2025_top_line_views" />
</NotificationTopLineView>
diff --git a/core/res/res/layout/notification_2025_template_heads_up_base.xml b/core/res/res/layout/notification_2025_template_heads_up_base.xml
index e4ff835a3524..084ec7daa683 100644
--- a/core/res/res/layout/notification_2025_template_heads_up_base.xml
+++ b/core/res/res/layout/notification_2025_template_heads_up_base.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2014 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
diff --git a/core/res/res/layout/notification_2025_top_line_views.xml b/core/res/res/layout/notification_2025_top_line_views.xml
new file mode 100644
index 000000000000..74873463391e
--- /dev/null
+++ b/core/res/res/layout/notification_2025_top_line_views.xml
@@ -0,0 +1,159 @@
+<?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
+ -->
+<!--
+ This layout file should be included inside a NotificationTopLineView, sometimes after a
+ <TextView android:id="@+id/title"/>
+-->
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <TextView
+ android:id="@+id/app_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:visibility="?attr/notificationHeaderAppNameVisibility"
+ />
+
+ <TextView
+ android:id="@+id/header_text_secondary_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:visibility="gone"
+ />
+
+ <TextView
+ android:id="@+id/header_text_secondary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:visibility="gone"
+ android:singleLine="true"
+ />
+
+ <TextView
+ android:id="@+id/header_text_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:visibility="gone"
+ />
+
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:visibility="gone"
+ android:singleLine="true"
+ />
+
+ <TextView
+ android:id="@+id/time_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <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_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:showRelative="true"
+ android:singleLine="true"
+ android:visibility="gone"
+ />
+
+ <ViewStub
+ android:id="@+id/chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:layout="@layout/notification_template_part_chronometer"
+ android:visibility="gone"
+ />
+
+ <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:baseline="13dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_feedback_indicator"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_feedback_indicator"
+ />
+
+ <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"
+ />
+</merge>
+
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index e6295ea06177..4ff3f8825cc4 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -101,6 +101,13 @@
P.S this is a change only intended for wear devices. -->
<bool name="config_enableViewGroupScalingFading">true</bool>
- <!-- Allow the gesture to double tap the power button to trigger a target action. -->
- <bool name="config_doubleTapPowerGestureEnabled">false</bool>
+ <!-- Controls the double tap power button gesture to trigger a target action.
+ 0: Gesture is disabled
+ 1: Launch camera mode, allowing the user to disable/enable the double tap power gesture
+ from launching the camera application.
+ 2: Multi target mode, allowing the user to select one of the targets defined in
+ config_doubleTapPowerGestureMultiTargetDefaultAction and to disable/enable the double
+ tap power gesture from triggering the selected target action.
+ -->
+ <integer name="config_doubleTapPowerGestureMode">0</integer>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 728c856f5855..8372aecf0d27 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7572,25 +7572,31 @@
<!-- NotificationProgressDrawable class -->
<!-- ================================== -->
- <!-- Drawable used to render a segmented bar, with segments and points. -->
+ <!-- Drawable used to render a notification progress bar, with segments and points. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawable">
- <!-- Default color for the parts. -->
+ <!-- The gap between two segments. -->
<attr name="segSegGap" format="dimension" />
+ <!-- The gap between a segment and a point. -->
<attr name="segPointGap" format="dimension" />
</declare-styleable>
<!-- Used to config the segments of a NotificationProgressDrawable. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawableSegments">
- <!-- Height of the solid segments -->
+ <!-- TODO: b/372908709 - maybe move this to NotificationProgressBar, because that's the only
+ place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
+ above. -->
+ <!-- Minimum required drawing width. The drawing width refers to the width after
+ the original segments have been adjusted for the neighboring Points and gaps. This is
+ enforced by stretching the segments that are too short. -->
+ <attr name="minWidth" format="dimension" />
+ <!-- Height of the solid segments. -->
<attr name="height" />
- <!-- Height of the faded segments -->
- <attr name="fadedHeight" format="dimension"/>
+ <!-- Height of the faded segments. -->
+ <attr name="fadedHeight" format="dimension" />
<!-- Corner radius of the segment rect. -->
<attr name="cornerRadius" format="dimension" />
- <!-- Default color of the segment. -->
- <attr name="color" />
</declare-styleable>
<!-- Used to config the points of a NotificationProgressDrawable. -->
@@ -7602,8 +7608,6 @@
<attr name="inset" />
<!-- Corner radius of the point rect. -->
<attr name="cornerRadius"/>
- <!-- Default color of the point rect. -->
- <attr name="color" />
</declare-styleable>
<!-- ========================== -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c50c5e9d3341..ce9a0c636d62 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2083,6 +2083,18 @@
See com.android.server.timezonedetector.TimeZoneDetectorStrategy for more information. -->
<bool name="config_supportTelephonyTimeZoneFallback" translatable="false">true</bool>
+ <!-- Whether the time notifications feature is enabled. Settings this to false means the feature
+ cannot be used. Setting this to true means the feature can be enabled on the device. -->
+ <bool name="config_enableTimeZoneNotificationsSupported" translatable="false">true</bool>
+
+ <!-- Whether the time zone notifications tracking feature is enabled. Settings this to false
+ means the feature cannot be used. -->
+ <bool name="config_enableTimeZoneNotificationsTrackingSupported" translatable="false">true</bool>
+
+ <!-- Whether the time zone manual change tracking feature is enabled. Settings this to false
+ means the feature cannot be used. -->
+ <bool name="config_enableTimeZoneManualChangeTrackingSupported" translatable="false">true</bool>
+
<!-- Whether to enable network location overlay which allows network location provider to be
replaced by an app at run-time. When disabled, only the
config_networkLocationProviderPackageName package will be searched for network location
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 2adb79118ed9..d6b8704a978b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -608,12 +608,23 @@
<!-- Size of the feedback indicator for notifications -->
<dimen name="notification_feedback_size">20dp</dimen>
+ <!-- Size of the (work) profile badge for notifications -->
+ <dimen name="notification_badge_size">12dp</dimen>
+
+ <!-- Size of the (work) profile badge for notifications (2025 redesign version).
+ Scales with font size. Chosen to look good alongside notification_subtext_size text. -->
+ <dimen name="notification_2025_badge_size">14sp</dimen>
+
+ <!-- Baseline for aligning icons in the top line (like the work profile icon or alerting icon)
+ to the text properly. This is equal to notification_2025_badge_size - 2sp. -->
+ <dimen name="notification_2025_badge_baseline">12sp</dimen>
+
+ <!-- Spacing for the top line icons (e.g. the work profile badge). -->
+ <dimen name="notification_2025_badge_margin">4dp</dimen>
+
<!-- Size of the phishing alert for notifications -->
<dimen name="notification_phishing_alert_size">@dimen/notification_badge_size</dimen>
- <!-- Size of the profile badge for notifications -->
- <dimen name="notification_badge_size">12dp</dimen>
-
<!-- Size of the alerted icon for notifications -->
<dimen name="notification_alerted_size">@dimen/notification_badge_size</dimen>
@@ -888,6 +899,8 @@
<dimen name="notification_progress_segSeg_gap">4dp</dimen>
<!-- The gap between a segment and a point in the notification progress bar -->
<dimen name="notification_progress_segPoint_gap">4dp</dimen>
+ <!-- The minimum required drawing width of the notification progress bar segments -->
+ <dimen name="notification_progress_segments_min_width">16dp</dimen>
<!-- The height of the notification progress bar segments -->
<dimen name="notification_progress_segments_height">6dp</dimen>
<!-- The height of the notification progress bar faded segments -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6313054e47f5..debc5e9a0dce 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -853,6 +853,9 @@
<!-- Text shown when viewing channel settings for notifications related to vpn status -->
<string name="notification_channel_vpn">VPN status</string>
+ <!-- Text shown when viewing channel settings for notifications related to system time -->
+ <string name="notification_channel_system_time">Time and time zones</string>
+
<!-- Notification channel name. This channel sends high-priority alerts from the user's IT admin for key updates about the user's work device or work profile. -->
<string name="notification_channel_device_admin">Alerts from your IT admin</string>
@@ -881,6 +884,10 @@
<string name="notification_channel_accessibility_magnification">Magnification</string>
<!-- Text shown when viewing channel settings for notifications related to accessibility
+ hearing device. [CHAR_LIMIT=NONE]-->
+ <string name="notification_channel_accessibility_hearing_device">Hearing device</string>
+
+ <!-- Text shown when viewing channel settings for notifications related to accessibility
security policy. [CHAR_LIMIT=NONE]-->
<string name="notification_channel_accessibility_security_policy">Accessibility usage</string>
@@ -3875,6 +3882,12 @@
<string name="carrier_app_notification_title">New SIM inserted</string>
<string name="carrier_app_notification_text">Tap to set it up</string>
+ <!-- Time zone notification strings -->
+ <!-- Title for time zone change notifications -->
+ <string name="time_zone_change_notification_title">Your time zone changed</string>
+ <!-- Body for time zone change notifications -->
+ <string name="time_zone_change_notification_body">You\'re now in <xliff:g id="time_zone_display_name">%1$s</xliff:g> (<xliff:g id="time_zone_offset">%2$s</xliff:g>)</string>
+
<!-- Date/Time picker dialogs strings -->
<!-- The title of the time picker dialog. [CHAR LIMIT=NONE] -->
@@ -4985,6 +4998,19 @@
<!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. -->
<string name="accessibility_magnification_chooser_text">Magnification</string>
+ <!-- Notification title for switching input to the phone's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_switch_phone_mic_notification_title">Switch to phone mic?</string>
+ <!-- Notification title for switching input to the hearing device's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_switch_hearing_mic_notification_title">Switch to hearing aid mic?</string>
+ <!-- Notification content for switching input to the phone's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_switch_phone_mic_notification_text">For better sound or if your hearing aid battery is low. This only switches your mic during the call.</string>
+ <!-- Notification content for switching input to the hearing device's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_switch_hearing_mic_notification_text">You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call.</string>
+ <!-- Notification action button. Click it will switch the input between phone's microphone and hearing device's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_notification_switch_button">Switch</string>
+ <!-- Notification action button. Click it will open the bluetooth device details page for this hearing device. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_notification_settings_button">Settings</string>
+
<!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
<string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
<!-- Message shown when switching to a user [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 24e7057320ff..6c014e93d4cc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -575,6 +575,7 @@
<java-symbol type="dimen" name="notification_top_pad_large_text" />
<java-symbol type="dimen" name="notification_top_pad_large_text_narrow" />
<java-symbol type="dimen" name="notification_badge_size" />
+ <java-symbol type="dimen" name="notification_2025_badge_size" />
<java-symbol type="dimen" name="immersive_mode_cling_width" />
<java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
<java-symbol type="dimen" name="circular_display_mask_thickness" />
@@ -2377,6 +2378,9 @@
<java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" />
<java-symbol type="bool" name="config_enableTelephonyTimeZoneDetection" />
<java-symbol type="bool" name="config_supportTelephonyTimeZoneFallback" />
+ <java-symbol type="bool" name="config_enableTimeZoneNotificationsSupported" />
+ <java-symbol type="bool" name="config_enableTimeZoneNotificationsTrackingSupported" />
+ <java-symbol type="bool" name="config_enableTimeZoneManualChangeTrackingSupported" />
<java-symbol type="bool" name="config_autoResetAirplaneMode" />
<java-symbol type="string" name="config_notificationAccessConfirmationActivity" />
<java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" />
@@ -3839,6 +3843,13 @@
<java-symbol type="string" name="reduce_bright_colors_feature_name" />
<java-symbol type="string" name="one_handed_mode_feature_name" />
+ <java-symbol type="string" name="hearing_device_switch_phone_mic_notification_title" />
+ <java-symbol type="string" name="hearing_device_switch_hearing_mic_notification_title" />
+ <java-symbol type="string" name="hearing_device_switch_phone_mic_notification_text" />
+ <java-symbol type="string" name="hearing_device_switch_hearing_mic_notification_text" />
+ <java-symbol type="string" name="hearing_device_notification_switch_button" />
+ <java-symbol type="string" name="hearing_device_notification_settings_button" />
+
<!-- com.android.internal.widget.RecyclerView -->
<java-symbol type="id" name="item_touch_helper_previous_elevation"/>
<java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
@@ -3939,6 +3950,7 @@
<java-symbol type="dimen" name="notification_progress_tracker_height" />
<java-symbol type="dimen" name="notification_progress_segSeg_gap" />
<java-symbol type="dimen" name="notification_progress_segPoint_gap" />
+ <java-symbol type="dimen" name="notification_progress_segments_min_width" />
<java-symbol type="dimen" name="notification_progress_segments_height" />
<java-symbol type="dimen" name="notification_progress_segments_faded_height" />
<java-symbol type="dimen" name="notification_progress_segments_corner_radius" />
@@ -4018,6 +4030,7 @@
<java-symbol type="string" name="notification_channel_network_available" />
<java-symbol type="array" name="config_defaultCloudSearchServices" />
<java-symbol type="string" name="notification_channel_vpn" />
+ <java-symbol type="string" name="notification_channel_system_time" />
<java-symbol type="string" name="notification_channel_device_admin" />
<java-symbol type="string" name="notification_channel_alerts" />
<java-symbol type="string" name="notification_channel_retail_mode" />
@@ -4025,8 +4038,11 @@
<java-symbol type="string" name="notification_channel_heavy_weight_app" />
<java-symbol type="string" name="notification_channel_system_changes" />
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
+ <java-symbol type="string" name="notification_channel_accessibility_hearing_device" />
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
<java-symbol type="string" name="notification_channel_display" />
+ <java-symbol type="string" name="time_zone_change_notification_title" />
+ <java-symbol type="string" name="time_zone_change_notification_body" />
<java-symbol type="string" name="config_defaultAutofillService" />
<java-symbol type="string" name="config_defaultFieldClassificationService" />
<java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 6538ce85457c..3d6e1225bd92 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -16,6 +16,8 @@
package android.app;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -25,8 +27,12 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -35,6 +41,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -42,6 +49,7 @@ import org.junit.runner.RunWith;
import java.time.Instant;
import java.time.InstantSource;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -50,14 +58,24 @@ public class NotificationManagerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- private Context mContext;
private NotificationManagerWithMockService mNotificationManager;
private final FakeClock mClock = new FakeClock();
+ private PackageTestableContext mContext;
+
@Before
public void setUp() {
- mContext = ApplicationProvider.getApplicationContext();
+ mContext = new PackageTestableContext(ApplicationProvider.getApplicationContext());
mNotificationManager = new NotificationManagerWithMockService(mContext, mClock);
+
+ // Caches must be in test mode in order to be used in tests.
+ PropertyInvalidatedCache.setTestMode(true);
+ mNotificationManager.setChannelCacheToTestMode();
+ }
+
+ @After
+ public void tearDown() {
+ PropertyInvalidatedCache.setTestMode(false);
}
@Test
@@ -243,12 +261,161 @@ public class NotificationManagerTest {
anyInt(), any(), anyInt());
}
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannel_cachedUntilInvalidated() throws Exception {
+ // Invalidate the cache first because the cache won't do anything until then
+ NotificationManager.invalidateNotificationChannelCache();
+
+ // It doesn't matter what the returned contents are, as long as we return a channel.
+ // This setup must set up getNotificationChannels(), as that's the method called.
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
+
+ // ask for the same channel 100 times without invalidating the cache
+ for (int i = 0; i < 100; i++) {
+ NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
+ }
+
+ // invalidate the cache; then ask again
+ NotificationManager.invalidateNotificationChannelCache();
+ NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
+
+ verify(mNotificationManager.mBackendService, times(2))
+ .getNotificationChannels(any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannel_sameApp_oneCall() throws Exception {
+ NotificationManager.invalidateNotificationChannelCache();
+
+ NotificationChannel c1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel c2 = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_NONE);
+
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, c2)));
+
+ assertThat(mNotificationManager.getNotificationChannel("id1")).isEqualTo(c1);
+ assertThat(mNotificationManager.getNotificationChannel("id2")).isEqualTo(c2);
+ assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
+
+ verify(mNotificationManager.mBackendService, times(1))
+ .getNotificationChannels(any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannels_cachedUntilInvalidated() throws Exception {
+ NotificationManager.invalidateNotificationChannelCache();
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
+
+ // ask for channels 100 times without invalidating the cache
+ for (int i = 0; i < 100; i++) {
+ List<NotificationChannel> unused = mNotificationManager.getNotificationChannels();
+ }
+
+ // invalidate the cache; then ask again
+ NotificationManager.invalidateNotificationChannelCache();
+ List<NotificationChannel> res = mNotificationManager.getNotificationChannels();
+
+ verify(mNotificationManager.mBackendService, times(2))
+ .getNotificationChannels(any(), any(), anyInt());
+ assertThat(res).containsExactlyElementsIn(List.of(exampleChannel()));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannel_channelAndConversationLookup() throws Exception {
+ NotificationManager.invalidateNotificationChannelCache();
+
+ // Full list of channels: c1; conv1 = child of c1; c2 is unrelated
+ NotificationChannel c1 = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel conv1 = new NotificationChannel("", "name_conversation",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ conv1.setConversationId("id", "id_conversation");
+ NotificationChannel c2 = new NotificationChannel("other", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), anyInt()))
+ .thenReturn(new ParceledListSlice<>(List.of(c1, conv1, c2)));
+
+ // Lookup for channel c1 and c2: returned as expected
+ assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(c1);
+ assertThat(mNotificationManager.getNotificationChannel("other")).isEqualTo(c2);
+
+ // Lookup for conv1 should return conv1
+ assertThat(mNotificationManager.getNotificationChannel("id", "id_conversation")).isEqualTo(
+ conv1);
+
+ // Lookup for a different conversation channel that doesn't exist, whose parent channel id
+ // is "id", should return c1
+ assertThat(mNotificationManager.getNotificationChannel("id", "nonexistent")).isEqualTo(c1);
+
+ // Lookup of a nonexistent channel is null
+ assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
+
+ // All of that should have been one call to getNotificationChannels()
+ verify(mNotificationManager.mBackendService, times(1))
+ .getNotificationChannels(any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannel_differentPackages() throws Exception {
+ NotificationManager.invalidateNotificationChannelCache();
+ final String pkg1 = "one";
+ final String pkg2 = "two";
+ final int userId = 0;
+ final int userId1 = 1;
+
+ // multiple channels with the same ID, but belonging to different packages/users
+ NotificationChannel channel1 = new NotificationChannel("id", "name1",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel channel2 = channel1.copy();
+ channel2.setName("name2");
+ NotificationChannel channel3 = channel1.copy();
+ channel3.setName("name3");
+
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
+ eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel1)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg2),
+ eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel2)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
+ eq(userId1))).thenReturn(new ParceledListSlice<>(List.of(channel3)));
+
+ // set our context to pretend to be from package 1 and userId 0
+ mContext.setParameters(pkg1, pkg1, userId);
+ assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel1);
+
+ // now package 2
+ mContext.setParameters(pkg2, pkg2, userId);
+ assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel2);
+
+ // now pkg1 for a different user
+ mContext.setParameters(pkg1, pkg1, userId1);
+ assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel3);
+
+ // Those should have been three different calls
+ verify(mNotificationManager.mBackendService, times(3))
+ .getNotificationChannels(any(), any(), anyInt());
+ }
+
private Notification exampleNotification() {
return new Notification.Builder(mContext, "channel")
.setSmallIcon(android.R.drawable.star_big_on)
.build();
}
+ private NotificationChannel exampleChannel() {
+ return new NotificationChannel("id", "channel_name",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ }
+
private static class NotificationManagerWithMockService extends NotificationManager {
private final INotificationManager mBackendService;
@@ -264,6 +431,48 @@ public class NotificationManagerTest {
}
}
+ // Helper context wrapper class where we can control just the return values of getPackageName,
+ // getOpPackageName, and getUserId (used in getNotificationChannels).
+ private static class PackageTestableContext extends ContextWrapper {
+ private String mPackage;
+ private String mOpPackage;
+ private Integer mUserId;
+
+ PackageTestableContext(Context base) {
+ super(base);
+ }
+
+ void setParameters(String packageName, String opPackageName, int userId) {
+ mPackage = packageName;
+ mOpPackage = opPackageName;
+ mUserId = userId;
+ }
+
+ @Override
+ public String getPackageName() {
+ if (mPackage != null) return mPackage;
+ return super.getPackageName();
+ }
+
+ @Override
+ public String getOpPackageName() {
+ if (mOpPackage != null) return mOpPackage;
+ return super.getOpPackageName();
+ }
+
+ @Override
+ public int getUserId() {
+ if (mUserId != null) return mUserId;
+ return super.getUserId();
+ }
+
+ @Override
+ public UserHandle getUser() {
+ if (mUserId != null) return UserHandle.of(mUserId);
+ return super.getUser();
+ }
+ }
+
private static class FakeClock implements InstantSource {
private long mNowMillis = 441644400000L;
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index ca6ad6fae46e..7be6950fb613 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2504,6 +2504,21 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressSegments() {
+ final List<Notification.ProgressStyle.Segment> segments = List.of(
+ new Notification.ProgressStyle.Segment(100).setColor(Color.WHITE),
+ new Notification.ProgressStyle.Segment(50).setColor(Color.RED),
+ new Notification.ProgressStyle.Segment(50).setColor(Color.BLUE)
+ );
+
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+ progressStyle1.setProgressSegments(segments);
+
+ assertThat(progressStyle1.getProgressSegments()).isEqualTo(segments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_addProgressPoint_dropsNegativePoints() {
// GIVEN
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2532,6 +2547,21 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressPoints() {
+ final List<Notification.ProgressStyle.Point> points = List.of(
+ new Notification.ProgressStyle.Point(0).setColor(Color.WHITE),
+ new Notification.ProgressStyle.Point(50).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(100).setColor(Color.BLUE)
+ );
+
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+ progressStyle1.setProgressPoints(points);
+
+ assertThat(progressStyle1.getProgressPoints()).isEqualTo(points);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_createProgressModel_ignoresPointsExceedingMax() {
// GIVEN
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
@@ -2673,11 +2703,58 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressIndeterminate() {
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+ progressStyle1.setProgressIndeterminate(true);
+ assertThat(progressStyle1.isProgressIndeterminate()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_styledByProgress_defaultValueTrue() {
final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
assertThat(progressStyle1.isStyledByProgress()).isTrue();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setStyledByProgress() {
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+ progressStyle1.setStyledByProgress(false);
+ assertThat(progressStyle1.isStyledByProgress()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_point() {
+ final int id = 1;
+ final int position = 10;
+ final int color = Color.RED;
+
+ final Notification.ProgressStyle.Point point =
+ new Notification.ProgressStyle.Point(position).setId(id).setColor(color);
+
+ assertEquals(id, point.getId());
+ assertEquals(position, point.getPosition());
+ assertEquals(color, point.getColor());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_segment() {
+ final int id = 1;
+ final int length = 100;
+ final int color = Color.RED;
+
+ final Notification.ProgressStyle.Segment segment =
+ new Notification.ProgressStyle.Segment(length).setId(id).setColor(color);
+
+ assertEquals(id, segment.getId());
+ assertEquals(length, segment.getLength());
+ assertEquals(color, segment.getColor());
+ }
+
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
index 0bf406c970f2..2bd3f4df9435 100644
--- a/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
+++ b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
@@ -17,6 +17,7 @@
package com.android.internal.notification;
import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_HEARING_DEVICE;
import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION;
import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY;
import static com.android.internal.notification.SystemNotificationChannels.ACCOUNT;
@@ -90,8 +91,8 @@ public class SystemNotificationChannelsTest {
DEVELOPER_IMPORTANT, UPDATES, NETWORK_STATUS, NETWORK_ALERTS,
NETWORK_AVAILABLE, VPN, DEVICE_ADMIN, ALERTS, RETAIL_MODE, USB,
FOREGROUND_SERVICE, HEAVY_WEIGHT_APP, SYSTEM_CHANGES,
- ACCESSIBILITY_MAGNIFICATION, ACCESSIBILITY_SECURITY_POLICY,
- ABUSIVE_BACKGROUND_APPS);
+ ACCESSIBILITY_MAGNIFICATION, ACCESSIBILITY_HEARING_DEVICE,
+ ACCESSIBILITY_SECURITY_POLICY, ABUSIVE_BACKGROUND_APPS);
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index d26bb35e5481..f105ec305eab 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -20,12 +20,13 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.Notification.ProgressStyle;
import android.graphics.Color;
+import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.internal.widget.NotificationProgressDrawable.Part;
-import com.android.internal.widget.NotificationProgressDrawable.Point;
-import com.android.internal.widget.NotificationProgressDrawable.Segment;
+import com.android.internal.widget.NotificationProgressBar.Part;
+import com.android.internal.widget.NotificationProgressBar.Point;
+import com.android.internal.widget.NotificationProgressBar.Segment;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,183 +38,303 @@ import java.util.List;
public class NotificationProgressBarTest {
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_segmentsIsEmpty() {
+ public void processAndConvertToParts_segmentsIsEmpty() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 50;
int progressMax = 100;
- boolean isStyledByProgress = true;
- NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- progressMax,
- isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_segmentsLengthNotMatchingProgressMax() {
+ public void processAndConvertToParts_segmentsLengthNotMatchingProgressMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50));
segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 50;
int progressMax = 100;
- boolean isStyledByProgress = true;
- NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- progressMax,
- isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_segmentLengthIsNegative() {
+ public void processAndConvertToParts_segmentLengthIsNegative() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(-50));
segments.add(new ProgressStyle.Segment(150));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 50;
int progressMax = 100;
- boolean isStyledByProgress = true;
- NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- progressMax,
- isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_segmentLengthIsZero() {
+ public void processAndConvertToParts_segmentLengthIsZero() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(0));
segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 50;
int progressMax = 100;
- boolean isStyledByProgress = true;
- NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- progressMax,
- isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_progressIsNegative() {
+ public void processAndConvertToParts_progressIsNegative() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = -50;
int progressMax = 100;
- boolean isStyledByProgress = true;
- NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- progressMax,
- isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test
- public void processAndConvertToDrawableParts_progressIsZero() {
+ public void processAndConvertToParts_progressIsZero() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 0;
int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 300, Color.RED)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
boolean isStyledByProgress = true;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 300,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 40% opacity
int fadedRed = 0x66FF0000;
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 300, fadedRed, true)));
- List<Part> expected = new ArrayList<>(List.of(new Segment(1f, fadedRed, true)));
-
- assertThat(parts).isEqualTo(expected);
+ assertThat(p.second).isEqualTo(0);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test
- public void processAndConvertToDrawableParts_progressAtMax() {
+ public void processAndConvertToParts_progressAtMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 100;
int progressMax = 100;
- boolean isStyledByProgress = true;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
- List<Part> expected = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+ List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
- assertThat(parts).isEqualTo(expected);
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 300, Color.RED)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 300,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ assertThat(p.second).isEqualTo(300);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_progressAboveMax() {
+ public void processAndConvertToParts_progressAboveMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 150;
int progressMax = 100;
- boolean isStyledByProgress = true;
- NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- progressMax, isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_pointPositionIsNegative() {
+ public void processAndConvertToParts_pointPositionIsNegative() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
points.add(new ProgressStyle.Point(-50).setColor(Color.RED));
int progress = 50;
int progressMax = 100;
- boolean isStyledByProgress = true;
- NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- progressMax,
- isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_pointPositionAboveMax() {
+ public void processAndConvertToParts_pointPositionAboveMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
points.add(new ProgressStyle.Point(150).setColor(Color.RED));
int progress = 50;
int progressMax = 100;
- boolean isStyledByProgress = true;
- NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- progressMax,
- isStyledByProgress);
+ NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ progressMax);
}
@Test
- public void processAndConvertToDrawableParts_multipleSegmentsWithoutPoints() {
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 60;
int progressMax = 100;
- boolean isStyledByProgress = true;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(
+ segments, points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(
+ new Segment(0.50f, Color.RED),
+ new Segment(0.50f, Color.GREEN)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 146, Color.RED),
+ new NotificationProgressDrawable.Segment(150, 300, Color.GREEN)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 300,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 40% opacity
int fadedGreen = 0x6600FF00;
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 146, Color.RED),
+ new NotificationProgressDrawable.Segment(150, 180, Color.GREEN),
+ new NotificationProgressDrawable.Segment(180, 300, fadedGreen, true)));
+
+ assertThat(p.second).isEqualTo(180);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 60;
+ int progressMax = 100;
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
- List<Part> expected = new ArrayList<>(List.of(
+ List<Part> expectedParts = new ArrayList<>(List.of(
new Segment(0.50f, Color.RED),
- new Segment(0.10f, Color.GREEN),
- new Segment(0.40f, fadedGreen, true)));
+ new Segment(0.50f, Color.GREEN)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = false;
+
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 146, Color.RED),
+ new NotificationProgressDrawable.Segment(150, 300, Color.GREEN)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 300,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ // Colors with 40% opacity
+ int fadedGreen = 0x6600FF00;
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 146, Color.RED),
+ new NotificationProgressDrawable.Segment(150, 176, Color.GREEN),
+ new NotificationProgressDrawable.Segment(180, 300, fadedGreen, true)));
- assertThat(parts).isEqualTo(expected);
+ assertThat(p.second).isEqualTo(180);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test
- public void processAndConvertToDrawableParts_singleSegmentWithPoints() {
+ public void processAndConvertToParts_singleSegmentWithPoints() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
@@ -223,31 +344,77 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
int progressMax = 100;
- boolean isStyledByProgress = true;
- // Colors with 40% opacity
- int fadedBlue = 0x660000FF;
- int fadedYellow = 0x66FFFF00;
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
- List<Part> expected = new ArrayList<>(List.of(
+ List<Part> expectedParts = new ArrayList<>(List.of(
new Segment(0.15f, Color.BLUE),
- new Point(null, Color.RED),
+ new Point(Color.RED),
new Segment(0.10f, Color.BLUE),
- new Point(null, Color.BLUE),
+ new Point(Color.BLUE),
new Segment(0.35f, Color.BLUE),
- new Point(null, Color.BLUE),
- new Segment(0.15f, fadedBlue, true),
- new Point(null, fadedYellow, true),
- new Segment(0.25f, fadedBlue, true)));
+ new Point(Color.BLUE),
+ new Segment(0.15f, Color.BLUE),
+ new Point(Color.YELLOW),
+ new Segment(0.25f, Color.BLUE)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 35, Color.BLUE),
+ new NotificationProgressDrawable.Point(39, 51, Color.RED),
+ new NotificationProgressDrawable.Segment(55, 65, Color.BLUE),
+ new NotificationProgressDrawable.Point(69, 81, Color.BLUE),
+ new NotificationProgressDrawable.Segment(85, 170, Color.BLUE),
+ new NotificationProgressDrawable.Point(174, 186, Color.BLUE),
+ new NotificationProgressDrawable.Segment(190, 215, Color.BLUE),
+ new NotificationProgressDrawable.Point(219, 231, Color.YELLOW),
+ new NotificationProgressDrawable.Segment(235, 300, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 300,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- assertThat(parts).isEqualTo(expected);
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ int fadedYellow = 0x66FFFF00;
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 34.219177F, Color.BLUE),
+ new NotificationProgressDrawable.Point(38.219177F, 50.219177F, Color.RED),
+ new NotificationProgressDrawable.Segment(54.219177F, 70.21918F, Color.BLUE),
+ new NotificationProgressDrawable.Point(74.21918F, 86.21918F, Color.BLUE),
+ new NotificationProgressDrawable.Segment(90.21918F, 172.38356F, Color.BLUE),
+ new NotificationProgressDrawable.Point(176.38356F, 188.38356F, Color.BLUE),
+ new NotificationProgressDrawable.Segment(192.38356F, 217.0137F, fadedBlue,
+ true),
+ new NotificationProgressDrawable.Point(221.0137F, 233.0137F, fadedYellow),
+ new NotificationProgressDrawable.Segment(237.0137F, 300F, fadedBlue,
+ true)));
+
+ assertThat(p.second).isEqualTo(182.38356F);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test
- public void processAndConvertToDrawableParts_multipleSegmentsWithPoints() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -258,32 +425,81 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
int progressMax = 100;
- boolean isStyledByProgress = true;
-
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
- // Colors with 40% opacity
- int fadedGreen = 0x6600FF00;
- int fadedYellow = 0x66FFFF00;
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
- List<Part> expected = new ArrayList<>(List.of(
+ List<Part> expectedParts = new ArrayList<>(List.of(
new Segment(0.15f, Color.RED),
- new Point(null, Color.RED),
+ new Point(Color.RED),
new Segment(0.10f, Color.RED),
- new Point(null, Color.BLUE),
+ new Point(Color.BLUE),
new Segment(0.25f, Color.RED),
new Segment(0.10f, Color.GREEN),
- new Point(null, Color.BLUE),
- new Segment(0.15f, fadedGreen, true),
- new Point(null, fadedYellow, true),
- new Segment(0.25f, fadedGreen, true)));
+ new Point(Color.BLUE),
+ new Segment(0.15f, Color.GREEN),
+ new Point(Color.YELLOW),
+ new Segment(0.25f, Color.GREEN)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 35, Color.RED),
+ new NotificationProgressDrawable.Point(39, 51, Color.RED),
+ new NotificationProgressDrawable.Segment(55, 65, Color.RED),
+ new NotificationProgressDrawable.Point(69, 81, Color.BLUE),
+ new NotificationProgressDrawable.Segment(85, 146, Color.RED),
+ new NotificationProgressDrawable.Segment(150, 170, Color.GREEN),
+ new NotificationProgressDrawable.Point(174, 186, Color.BLUE),
+ new NotificationProgressDrawable.Segment(190, 215, Color.GREEN),
+ new NotificationProgressDrawable.Point(219, 231, Color.YELLOW),
+ new NotificationProgressDrawable.Segment(235, 300, Color.GREEN)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 300,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- assertThat(parts).isEqualTo(expected);
+ // Colors with 40% opacity
+ int fadedGreen = 0x6600FF00;
+ int fadedYellow = 0x66FFFF00;
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 34.095238F, Color.RED),
+ new NotificationProgressDrawable.Point(38.095238F, 50.095238F, Color.RED),
+ new NotificationProgressDrawable.Segment(54.095238F, 70.09524F, Color.RED),
+ new NotificationProgressDrawable.Point(74.09524F, 86.09524F, Color.BLUE),
+ new NotificationProgressDrawable.Segment(90.09524F, 148.9524F, Color.RED),
+ new NotificationProgressDrawable.Segment(152.95238F, 172.7619F,
+ Color.GREEN),
+ new NotificationProgressDrawable.Point(176.7619F, 188.7619F, Color.BLUE),
+ new NotificationProgressDrawable.Segment(192.7619F, 217.33333F,
+ fadedGreen, true),
+ new NotificationProgressDrawable.Point(221.33333F, 233.33333F,
+ fadedYellow),
+ new NotificationProgressDrawable.Segment(237.33333F, 299.99997F,
+ fadedGreen, true)));
+
+ assertThat(p.second).isEqualTo(182.7619F);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
@Test
- public void processAndConvertToDrawableParts_multipleSegmentsWithPoints_notStyledByProgress() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -293,21 +509,251 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
int progressMax = 100;
- boolean isStyledByProgress = false;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
- List<Part> expected = new ArrayList<>(List.of(
+ List<Part> expectedParts = new ArrayList<>(List.of(
new Segment(0.15f, Color.RED),
- new Point(null, Color.RED),
+ new Point(Color.RED),
new Segment(0.10f, Color.RED),
- new Point(null, Color.BLUE),
+ new Point(Color.BLUE),
new Segment(0.25f, Color.RED),
new Segment(0.25f, Color.GREEN),
- new Point(null, Color.YELLOW),
+ new Point(Color.YELLOW),
new Segment(0.25f, Color.GREEN)));
- assertThat(parts).isEqualTo(expected);
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 35, Color.RED),
+ new NotificationProgressDrawable.Point(39, 51, Color.RED),
+ new NotificationProgressDrawable.Segment(55, 65, Color.RED),
+ new NotificationProgressDrawable.Point(69, 81, Color.BLUE),
+ new NotificationProgressDrawable.Segment(85, 146, Color.RED),
+ new NotificationProgressDrawable.Segment(150, 215, Color.GREEN),
+ new NotificationProgressDrawable.Point(219, 231, Color.YELLOW),
+ new NotificationProgressDrawable.Segment(235, 300, Color.GREEN)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = false;
+
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 300,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Segment(0, 34.296295F, Color.RED),
+ new NotificationProgressDrawable.Point(38.296295F, 50.296295F, Color.RED),
+ new NotificationProgressDrawable.Segment(54.296295F, 70.296295F, Color.RED),
+ new NotificationProgressDrawable.Point(74.296295F, 86.296295F, Color.BLUE),
+ new NotificationProgressDrawable.Segment(90.296295F, 149.62962F, Color.RED),
+ new NotificationProgressDrawable.Segment(153.62962F, 216.8148F,
+ Color.GREEN),
+ new NotificationProgressDrawable.Point(220.81482F, 232.81482F,
+ Color.YELLOW),
+ new NotificationProgressDrawable.Segment(236.81482F, 300, Color.GREEN)));
+
+ assertThat(p.second).isEqualTo(182.9037F);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ // The only difference from the `zeroWidthDrawableSegment` test below is the longer
+ // segmentMinWidth (= 16dp).
+ @Test
+ public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ int progress = 1000;
+ int progressMax = 1000;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(
+ segments, points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(
+ new Point(Color.BLUE),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.2f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
+ new Segment(0.4f, Color.BLUE)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 200;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE),
+ new NotificationProgressDrawable.Segment(16, 16, Color.BLUE),
+ new NotificationProgressDrawable.Segment(20, 56, Color.BLUE),
+ new NotificationProgressDrawable.Segment(60, 116, Color.BLUE),
+ new NotificationProgressDrawable.Segment(120, 200, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 200,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE),
+ new NotificationProgressDrawable.Segment(16, 32, Color.BLUE),
+ new NotificationProgressDrawable.Segment(36, 69.41936F, Color.BLUE),
+ new NotificationProgressDrawable.Segment(73.41936F, 124.25807F, Color.BLUE),
+ new NotificationProgressDrawable.Segment(128.25807F, 200, Color.BLUE)));
+
+ assertThat(p.second).isEqualTo(200);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ // The only difference from the `negativeWidthDrawableSegment` test above is the shorter
+ // segmentMinWidth (= 10dp).
+ @Test
+ public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ int progress = 1000;
+ int progressMax = 1000;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(
+ segments, points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(
+ new Point(Color.BLUE),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.2f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
+ new Segment(0.4f, Color.BLUE)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 200;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE),
+ new NotificationProgressDrawable.Segment(16, 16, Color.BLUE),
+ new NotificationProgressDrawable.Segment(20, 56, Color.BLUE),
+ new NotificationProgressDrawable.Segment(60, 116, Color.BLUE),
+ new NotificationProgressDrawable.Segment(120, 200, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 10;
+ boolean isStyledByProgress = true;
+
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 200,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE),
+ new NotificationProgressDrawable.Segment(16, 26, Color.BLUE),
+ new NotificationProgressDrawable.Segment(30, 64.169014F, Color.BLUE),
+ new NotificationProgressDrawable.Segment(68.169014F, 120.92958F,
+ Color.BLUE),
+ new NotificationProgressDrawable.Segment(124.92958F, 200, Color.BLUE)));
+
+ assertThat(p.second).isEqualTo(200);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void maybeStretchAndRescaleSegments_noStretchingNecessary() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(300).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(400).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(Color.BLUE));
+ int progress = 1000;
+ int progressMax = 1000;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(
+ segments, points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(List.of(
+ new Point(Color.BLUE),
+ new Segment(0.2f, Color.BLUE),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.3f, Color.BLUE),
+ new Segment(0.4f, Color.BLUE)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 200;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<NotificationProgressDrawable.Part> drawableParts =
+ NotificationProgressBar.processAndConvertToDrawableParts(parts, drawableWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon
+ );
+
+ List<NotificationProgressDrawable.Part> expectedDrawableParts = new ArrayList<>(
+ List.of(new NotificationProgressDrawable.Point(0, 12, Color.BLUE),
+ new NotificationProgressDrawable.Segment(16, 36, Color.BLUE),
+ new NotificationProgressDrawable.Segment(40, 56, Color.BLUE),
+ new NotificationProgressDrawable.Segment(60, 116, Color.BLUE),
+ new NotificationProgressDrawable.Segment(120, 200, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 10;
+ boolean isStyledByProgress = true;
+
+ Pair<List<NotificationProgressDrawable.Part>, Float> p =
+ NotificationProgressBar.maybeStretchAndRescaleSegments(parts, drawableParts,
+ segmentMinWidth, pointRadius, (float) progress / progressMax, 200,
+ isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ assertThat(p.second).isEqualTo(200);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
}
diff --git a/core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java
index e368d2815855..cb8b5ce245b6 100644
--- a/core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -48,12 +48,14 @@ public class TimeZoneCapabilitiesTest {
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED);
TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED);
{
TimeZoneCapabilities one = builder1.build();
TimeZoneCapabilities two = builder2.build();
@@ -115,6 +117,13 @@ public class TimeZoneCapabilitiesTest {
TimeZoneCapabilities two = builder2.build();
assertEquals(one, two);
}
+
+ builder1.setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
}
@Test
@@ -123,7 +132,8 @@ public class TimeZoneCapabilitiesTest {
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
- .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED);
assertRoundTripParcelable(builder.build());
builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -137,6 +147,9 @@ public class TimeZoneCapabilitiesTest {
builder.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
+
+ builder.setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ assertRoundTripParcelable(builder.build());
}
@Test
@@ -151,6 +164,7 @@ public class TimeZoneCapabilitiesTest {
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED)
.build();
TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -175,6 +189,7 @@ public class TimeZoneCapabilitiesTest {
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
.build();
TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -191,6 +206,7 @@ public class TimeZoneCapabilitiesTest {
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
.build();
{
@@ -204,6 +220,7 @@ public class TimeZoneCapabilitiesTest {
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -221,6 +238,7 @@ public class TimeZoneCapabilitiesTest {
.setUseLocationEnabled(false)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -238,6 +256,7 @@ public class TimeZoneCapabilitiesTest {
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -255,6 +274,25 @@ public class TimeZoneCapabilitiesTest {
.setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .build();
+
+ assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
+ }
+
+ {
+ TimeZoneCapabilities updatedCapabilities =
+ new TimeZoneCapabilities.Builder(capabilities)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED)
+ .build();
+
+ TimeZoneCapabilities expectedCapabilities =
+ new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setUseLocationEnabled(true)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED)
.build();
assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
diff --git a/core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java b/core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java
index 4ad3e41383aa..345e91268253 100644
--- a/core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java
+++ b/core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java
@@ -43,9 +43,11 @@ public class TimeZoneConfigurationTest {
TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
.setGeoDetectionEnabled(true)
+ .setNotificationsEnabled(true)
.build();
assertTrue(completeConfig.isComplete());
assertTrue(completeConfig.hasIsGeoDetectionEnabled());
+ assertTrue(completeConfig.hasIsNotificationsEnabled());
}
@Test
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 4c75ea4777da..957d1b835ec2 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -26,8 +26,8 @@ package {
java_library {
name: "wm_shell_protolog-groups",
srcs: [
- "src/com/android/wm/shell/protolog/ShellProtoLogGroup.java",
":protolog-common-src",
+ "src/com/android/wm/shell/protolog/ShellProtoLogGroup.java",
],
}
@@ -61,8 +61,8 @@ java_genrule {
name: "wm_shell_protolog_src",
srcs: [
":protolog-impl",
- ":wm_shell_protolog-groups",
":wm_shell-sources",
+ ":wm_shell_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
@@ -80,8 +80,8 @@ java_genrule {
java_genrule {
name: "generate-wm_shell_protolog.json",
srcs: [
- ":wm_shell_protolog-groups",
":wm_shell-sources",
+ ":wm_shell_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
@@ -97,8 +97,8 @@ java_genrule {
java_genrule {
name: "gen-wmshell.protolog.pb",
srcs: [
- ":wm_shell_protolog-groups",
":wm_shell-sources",
+ ":wm_shell_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
@@ -159,38 +159,39 @@ java_library {
android_library {
name: "WindowManager-Shell",
srcs: [
- "src/com/android/wm/shell/EventLogTags.logtags",
":wm_shell_protolog_src",
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
- ":wm_shell-sources-kt",
+ "src/com/android/wm/shell/EventLogTags.logtags",
":wm_shell-aidls",
":wm_shell-shared-aidls",
+ ":wm_shell-sources-kt",
],
resource_dirs: [
"res",
],
static_libs: [
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:iconloader_base",
+ "//packages/apps/Car/SystemUI/aconfig:com_android_systemui_car_flags_lib",
+ "PlatformAnimationLib",
+ "WindowManager-Shell-lite-proto",
+ "WindowManager-Shell-proto",
+ "WindowManager-Shell-shared",
+ "androidx-constraintlayout_constraintlayout",
"androidx.appcompat_appcompat",
- "androidx.core_core-ktx",
"androidx.arch.core_core-runtime",
- "androidx.datastore_datastore",
"androidx.compose.material3_material3",
- "androidx-constraintlayout_constraintlayout",
+ "androidx.core_core-ktx",
+ "androidx.datastore_datastore",
"androidx.dynamicanimation_dynamicanimation",
"androidx.recyclerview_recyclerview",
- "kotlinx-coroutines-android",
- "kotlinx-coroutines-core",
- "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
- "//frameworks/libs/systemui:iconloader_base",
"com_android_launcher3_flags_lib",
"com_android_wm_shell_flags_lib",
- "PlatformAnimationLib",
- "WindowManager-Shell-proto",
- "WindowManager-Shell-lite-proto",
- "WindowManager-Shell-shared",
- "perfetto_trace_java_protos",
"dagger2",
"jsr330",
+ "kotlinx-coroutines-android",
+ "kotlinx-coroutines-core",
+ "perfetto_trace_java_protos",
],
libs: [
// Soong fails to automatically add this dependency because all the
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 755f472ee22e..2fed1380b635 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -233,6 +233,16 @@ public class DesktopModeStatus {
}
/**
+ * Returns whether the multiple desktops feature is enabled for this device (both backend and
+ * frontend implementations).
+ */
+ public static boolean enableMultipleDesktops(@NonNull Context context) {
+ return Flags.enableMultipleDesktopsBackend()
+ && Flags.enableMultipleDesktopsFrontend()
+ && canEnterDesktopMode(context);
+ }
+
+ /**
* @return {@code true} if this device is requesting to show the app handle despite non
* necessarily enabling desktop mode
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 9f01316d5b5c..b098620fde2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -230,6 +230,11 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
return mDisplayAreasInfo.get(displayId);
}
+ @Nullable
+ public SurfaceControl getDisplayAreaLeash(int displayId) {
+ return mLeashes.get(displayId);
+ }
+
/**
* Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
* {@link DisplayAreaInfo#displayId}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java
new file mode 100644
index 000000000000..fc51c754e06b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.automotive;
+
+import com.android.wm.shell.dagger.WMSingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+
+@Module
+public abstract class AutoShellModule {
+ @WMSingleton
+ @Binds
+ abstract AutoTaskStackController provideTaskStackController(AutoTaskStackControllerImpl impl);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt
new file mode 100644
index 000000000000..caacdd355996
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.automotive
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.view.SurfaceControl
+
+/**
+ * Represents an auto task stack, which is always in multi-window mode.
+ *
+ * @property id The ID of the task stack.
+ * @property displayId The ID of the display the task stack is on.
+ * @property leash The surface control leash of the task stack.
+ */
+interface AutoTaskStack {
+ val id: Int
+ val displayId: Int
+ var leash: SurfaceControl
+}
+
+/**
+ * Data class representing the state of an auto task stack.
+ *
+ * @property bounds The bounds of the task stack.
+ * @property childrenTasksVisible Whether the child tasks of the stack are visible.
+ * @property layer The layer of the task stack.
+ */
+data class AutoTaskStackState(
+ val bounds: Rect = Rect(),
+ val childrenTasksVisible: Boolean,
+ val layer: Int
+)
+
+/**
+ * Data class representing a root task stack.
+ *
+ * @property id The ID of the root task stack
+ * @property displayId The ID of the display the root task stack is on.
+ * @property leash The surface control leash of the root task stack.
+ * @property rootTaskInfo The running task info of the root task.
+ */
+data class RootTaskStack(
+ override val id: Int,
+ override val displayId: Int,
+ override var leash: SurfaceControl,
+ var rootTaskInfo: ActivityManager.RunningTaskInfo
+) : AutoTaskStack
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt
new file mode 100644
index 000000000000..15fedac62af3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.automotive
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+
+/**
+ * Delegate interface for handling auto task stack transitions.
+ */
+interface AutoTaskStackTransitionHandlerDelegate {
+ /**
+ * Handles a transition request.
+ *
+ * @param transition The transition identifier.
+ * @param request The transition request information.
+ * @return An [AutoTaskStackTransaction] to be applied for the transition, or null if the
+ * animation is not handled by this delegate.
+ */
+ fun handleRequest(
+ transition: IBinder, request: TransitionRequestInfo
+ ): AutoTaskStackTransaction?
+
+ /**
+ * See [Transitions.TransitionHandler.startAnimation] for more details.
+ *
+ * @param changedTaskStacks Contains the states of the task stacks that were changed as a
+ * result of this transition. The key is the [AutoTaskStack.id] and the value is the
+ * corresponding [AutoTaskStackState].
+ */
+ fun startAnimation(
+ transition: IBinder,
+ changedTaskStacks: Map<Int, AutoTaskStackState>,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: TransitionFinishCallback
+ ): Boolean
+
+ /**
+ * See [Transitions.TransitionHandler.onTransitionConsumed] for more details.
+ *
+ * @param requestedTaskStacks contains the states of the task stacks that were requested in
+ * the transition. The key is the [AutoTaskStack.id] and the value is the corresponding
+ * [AutoTaskStackState].
+ */
+ fun onTransitionConsumed(
+ transition: IBinder,
+ requestedTaskStacks: Map<Int, AutoTaskStackState>,
+ aborted: Boolean, finishTransaction: SurfaceControl.Transaction?
+ )
+
+ /**
+ * See [Transitions.TransitionHandler.mergeAnimation] for more details.
+ *
+ * @param changedTaskStacks Contains the states of the task stacks that were changed as a
+ * result of this transition. The key is the [AutoTaskStack.id] and the value is the
+ * corresponding [AutoTaskStackState].
+ */
+ fun mergeAnimation(
+ transition: IBinder,
+ changedTaskStacks: Map<Int, AutoTaskStackState>,
+ info: TransitionInfo,
+ surfaceTransaction: SurfaceControl.Transaction,
+ mergeTarget: IBinder,
+ finishCallback: TransitionFinishCallback
+ )
+}
+
+
+/**
+ * Controller for managing auto task stacks.
+ */
+interface AutoTaskStackController {
+
+ var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate?
+ set
+
+ /**
+ * Map of task stack IDs to their states.
+ *
+ * This gets updated right before [AutoTaskStackTransitionHandlerDelegate.startAnimation] or
+ * [AutoTaskStackTransitionHandlerDelegate.onTransitionConsumed] is called.
+ */
+ val taskStackStateMap: Map<Int, AutoTaskStackState>
+ get
+
+ /**
+ * Creates a new multi-window root task.
+ *
+ * A root task stack is placed in the default TDA of the specified display by default.
+ * Once the root task is removed, the [AutoTaskStackController] no longer holds a reference to
+ * it.
+ *
+ * @param displayId The ID of the display to create the root task stack on.
+ * @param listener The listener for root task stack events.
+ */
+ @ShellMainThread
+ fun createRootTaskStack(displayId: Int, listener: RootTaskStackListener)
+
+
+ /**
+ * Sets the default root task stack (launch root) on a display. Calling it again with a
+ * different [rootTaskStackId] will simply replace the default root task stack on the display.
+ *
+ * Note: This is helpful for passively routing tasks to a specified container. If a display
+ * doesn't have a default root task stack set, all tasks will open in fullscreen and cover
+ * the entire default TDA by default.
+ *
+ * @param displayId The ID of the display.
+ * @param rootTaskStackId The ID of the root task stack, or null to clear the default.
+ */
+ @ShellMainThread
+ fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?)
+
+ /**
+ * Starts a transaction with the specified [transaction].
+ * Returns the transition identifier.
+ */
+ @ShellMainThread
+ fun startTransition(transaction: AutoTaskStackTransaction): IBinder?
+}
+
+internal sealed class TaskStackOperation {
+ data class ReparentTask(
+ val taskId: Int,
+ val parentTaskStackId: Int,
+ val onTop: Boolean
+ ) : TaskStackOperation()
+
+ data class SendPendingIntent(
+ val sender: PendingIntent,
+ val intent: Intent,
+ val options: Bundle?
+ ) : TaskStackOperation()
+
+ data class SetTaskStackState(
+ val taskStackId: Int,
+ val state: AutoTaskStackState
+ ) : TaskStackOperation()
+}
+
+data class AutoTaskStackTransaction internal constructor(
+ internal val operations: MutableList<TaskStackOperation> = mutableListOf()
+) {
+ constructor() : this(
+ mutableListOf()
+ )
+
+ /** See [WindowContainerTransaction.reparent] for more details. */
+ fun reparentTask(
+ taskId: Int,
+ parentTaskStackId: Int,
+ onTop: Boolean
+ ): AutoTaskStackTransaction {
+ operations.add(TaskStackOperation.ReparentTask(taskId, parentTaskStackId, onTop))
+ return this
+ }
+
+ /** See [WindowContainerTransaction.sendPendingIntent] for more details. */
+ fun sendPendingIntent(
+ sender: PendingIntent,
+ intent: Intent,
+ options: Bundle?
+ ): AutoTaskStackTransaction {
+ operations.add(TaskStackOperation.SendPendingIntent(sender, intent, options))
+ return this
+ }
+
+ /**
+ * Adds a set task stack state operation to the transaction.
+ *
+ * If an operation with the same task stack ID already exists, it is replaced with the new one.
+ *
+ * @param taskStackId The ID of the task stack.
+ * @param state The new state of the task stack.
+ * @return The transaction with the added operation.
+ */
+ fun setTaskStackState(taskStackId: Int, state: AutoTaskStackState): AutoTaskStackTransaction {
+ val existingOperation = operations.find {
+ it is TaskStackOperation.SetTaskStackState && it.taskStackId == taskStackId
+ }
+ if (existingOperation != null) {
+ val index = operations.indexOf(existingOperation)
+ operations[index] = TaskStackOperation.SetTaskStackState(taskStackId, state)
+ } else {
+ operations.add(TaskStackOperation.SetTaskStackState(taskStackId, state))
+ }
+ return this
+ }
+
+ /**
+ * Returns a map of task stack IDs to their states from the set task stack state operations.
+ *
+ * @return The map of task stack IDs to states.
+ */
+ fun getTaskStackStates(): Map<Int, AutoTaskStackState> {
+ val states = mutableMapOf<Int, AutoTaskStackState>()
+ operations.forEach { operation ->
+ if (operation is TaskStackOperation.SetTaskStackState) {
+ states[operation.taskStackId] = operation.state
+ }
+ }
+ return states
+ }
+}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
new file mode 100644
index 000000000000..f8f284238a98
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.automotive
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
+import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.os.IBinder
+import android.util.Log
+import android.util.Slog
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.systemui.car.Flags.autoTaskStackWindowing
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+import javax.inject.Inject
+
+const val TAG = "AutoTaskStackController"
+
+@WMSingleton
+class AutoTaskStackControllerImpl @Inject constructor(
+ val taskOrganizer: ShellTaskOrganizer,
+ @ShellMainThread private val shellMainThread: ShellExecutor,
+ val transitions: Transitions,
+ val shellInit: ShellInit,
+ val rootTdaOrganizer: RootTaskDisplayAreaOrganizer
+) : AutoTaskStackController, Transitions.TransitionHandler {
+ override var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate? = null
+ override val taskStackStateMap = mutableMapOf<Int, AutoTaskStackState>()
+
+ private val DBG = Log.isLoggable(TAG, Log.DEBUG)
+ private val taskStackMap = mutableMapOf<Int, AutoTaskStack>()
+ private val pendingTransitions = ArrayList<PendingTransition>()
+ private val mTaskStackStateTranslator = TaskStackStateTranslator()
+ private val appTasksMap = mutableMapOf<Int, ActivityManager.RunningTaskInfo>()
+ private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>()
+
+ init {
+ if (!autoTaskStackWindowing()) {
+ throw IllegalStateException("Failed to initialize" +
+ "AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.")
+ } else {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ fun onInit() {
+ transitions.addHandler(this)
+ }
+
+ /** Translates the [AutoTaskStackState] to relevant WM and surface transactions. */
+ inner class TaskStackStateTranslator {
+ // TODO(b/384946072): Move to an interface with 2 implementations, one for root task and
+ // other for TDA
+ fun applyVisibilityAndBounds(
+ wct: WindowContainerTransaction,
+ taskStack: AutoTaskStack,
+ state: AutoTaskStackState
+ ) {
+ if (taskStack !is RootTaskStack) {
+ Slog.e(TAG, "Unsupported task stack, unable to convertToWct")
+ return
+ }
+ wct.setBounds(taskStack.rootTaskInfo.token, state.bounds)
+ wct.reorder(taskStack.rootTaskInfo.token, /* onTop= */ state.childrenTasksVisible)
+ }
+
+ fun reorderLeash(
+ taskStack: AutoTaskStack,
+ state: AutoTaskStackState,
+ transaction: Transaction
+ ) {
+ if (taskStack !is RootTaskStack) {
+ Slog.e(TAG, "Unsupported task stack, unable to reorder leash")
+ return
+ }
+ Slog.d(TAG, "Setting the layer ${state.layer}")
+ transaction.setLayer(taskStack.leash, state.layer)
+ }
+
+ fun restoreLeash(taskStack: AutoTaskStack, transaction: Transaction) {
+ if (taskStack !is RootTaskStack) {
+ Slog.e(TAG, "Unsupported task stack, unable to restore leash")
+ return
+ }
+
+ val rootTdaInfo = rootTdaOrganizer.getDisplayAreaInfo(taskStack.displayId)
+ if (rootTdaInfo == null ||
+ rootTdaInfo.featureId != taskStack.rootTaskInfo.displayAreaFeatureId
+ ) {
+ Slog.e(TAG, "Cannot find the rootTDA for the root task stack ${taskStack.id}")
+ return
+ }
+ if (DBG) {
+ Slog.d(TAG, "Reparenting ${taskStack.id} leash to DA ${rootTdaInfo.featureId}")
+ }
+ transaction.reparent(
+ taskStack.leash,
+ rootTdaOrganizer.getDisplayAreaLeash(taskStack.displayId)
+ )
+ }
+ }
+
+ inner class RootTaskStackListenerAdapter(
+ val rootTaskStackListener: RootTaskStackListener,
+ ) : ShellTaskOrganizer.TaskListener {
+ private var rootTaskStack: RootTaskStack? = null
+
+ // TODO(b/384948029): Notify car service for all the children tasks' events
+ override fun onTaskAppeared(
+ taskInfo: ActivityManager.RunningTaskInfo?,
+ leash: SurfaceControl?
+ ) {
+ if (taskInfo == null) {
+ throw IllegalArgumentException("taskInfo can't be null in onTaskAppeared")
+ }
+ if (leash == null) {
+ throw IllegalArgumentException("leash can't be null in onTaskAppeared")
+ }
+ if (DBG) Slog.d(TAG, "onTaskAppeared = ${taskInfo.taskId}")
+
+ if (rootTaskStack == null) {
+ val rootTask =
+ RootTaskStack(taskInfo.taskId, taskInfo.displayId, leash, taskInfo)
+ taskStackMap[rootTask.id] = rootTask
+
+ rootTaskStack = rootTask;
+ rootTaskStackListener.onRootTaskStackCreated(rootTask);
+ return
+ }
+ appTasksMap[taskInfo.taskId] = taskInfo
+ rootTaskStackListener.onTaskAppeared(taskInfo, leash)
+ }
+
+ override fun onTaskInfoChanged(taskInfo: ActivityManager.RunningTaskInfo?) {
+ if (taskInfo == null) {
+ throw IllegalArgumentException("taskInfo can't be null in onTaskInfoChanged")
+ }
+ if (DBG) Slog.d(TAG, "onTaskInfoChanged = ${taskInfo.taskId}")
+ var previousRootTaskStackInfo = rootTaskStack ?: run {
+ Slog.e(TAG, "Received onTaskInfoChanged, when root task stack is null")
+ return@onTaskInfoChanged
+ }
+ rootTaskStack?.let {
+ if (taskInfo.taskId == previousRootTaskStackInfo.id) {
+ previousRootTaskStackInfo = previousRootTaskStackInfo.copy(rootTaskInfo = taskInfo)
+ taskStackMap[previousRootTaskStackInfo.id] = previousRootTaskStackInfo
+ rootTaskStack = previousRootTaskStackInfo;
+ rootTaskStackListener.onRootTaskStackInfoChanged(it)
+ return
+ }
+ }
+
+ appTasksMap[taskInfo.taskId] = taskInfo
+ rootTaskStackListener.onTaskInfoChanged(taskInfo)
+ }
+
+ override fun onTaskVanished(taskInfo: ActivityManager.RunningTaskInfo?) {
+ if (taskInfo == null) {
+ throw IllegalArgumentException("taskInfo can't be null in onTaskVanished")
+ }
+ if (DBG) Slog.d(TAG, "onTaskVanished = ${taskInfo.taskId}")
+ var rootTask = rootTaskStack ?: run {
+ Slog.e(TAG, "Received onTaskVanished, when root task stack is null")
+ return@onTaskVanished
+ }
+ if (taskInfo.taskId == rootTask.id) {
+ rootTask = rootTask.copy(rootTaskInfo = taskInfo)
+ rootTaskStack = rootTask
+ rootTaskStackListener.onRootTaskStackDestroyed(rootTask)
+ taskStackMap.remove(rootTask.id)
+ taskStackStateMap.remove(rootTask.id)
+ rootTaskStack = null
+ return
+ }
+ appTasksMap.remove(taskInfo.taskId)
+ rootTaskStackListener.onTaskVanished(taskInfo)
+ }
+
+ override fun onBackPressedOnTaskRoot(taskInfo: ActivityManager.RunningTaskInfo?) {
+ if (taskInfo == null) {
+ throw IllegalArgumentException("taskInfo can't be null in onBackPressedOnTaskRoot")
+ }
+ super.onBackPressedOnTaskRoot(taskInfo)
+ rootTaskStackListener.onBackPressedOnTaskRoot(taskInfo)
+ }
+ }
+
+ override fun createRootTaskStack(
+ displayId: Int,
+ listener: RootTaskStackListener
+ ) {
+ if (!autoTaskStackWindowing()) {
+ Slog.e(
+ TAG, "Failed to create root task stack as the " +
+ "auto_task_stack_windowing TS flag is disabled."
+ )
+ return
+ }
+ taskOrganizer.createRootTask(
+ displayId,
+ WINDOWING_MODE_MULTI_WINDOW,
+ RootTaskStackListenerAdapter(listener),
+ /* removeWithTaskOrganizer= */ true
+ )
+ }
+
+ override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) {
+ if (!autoTaskStackWindowing()) {
+ Slog.e(
+ TAG, "Failed to set default root task stack as the " +
+ "auto_task_stack_windowing TS flag is disabled."
+ )
+ return
+ }
+ var wct = WindowContainerTransaction()
+
+ // Clear the default root task stack if already set
+ defaultRootTaskPerDisplay[displayId]?.let { existingDefaultRootTaskStackId ->
+ (taskStackMap[existingDefaultRootTaskStackId] as? RootTaskStack)?.let { rootTaskStack ->
+ wct.setLaunchRoot(rootTaskStack.rootTaskInfo.token, null, null)
+ }
+ }
+
+ if (rootTaskStackId != null) {
+ var taskStack =
+ taskStackMap[rootTaskStackId] ?: run { return@setDefaultRootTaskStackOnDisplay }
+ if (DBG) Slog.d(TAG, "setting launch root for = ${taskStack.id}")
+ if (taskStack !is RootTaskStack) {
+ throw IllegalArgumentException(
+ "Cannot set a non root task stack as default root task " +
+ "stack"
+ )
+ }
+ wct.setLaunchRoot(
+ taskStack.rootTaskInfo.token,
+ intArrayOf(WINDOWING_MODE_UNDEFINED),
+ intArrayOf(
+ ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_RECENTS,
+
+ // TODO(b/386242708): Figure out if this flag will ever be used for automotive
+ // assistant. Based on output, remove it from here and fix the
+ // AssistantStackTests accordingly.
+ ACTIVITY_TYPE_ASSISTANT
+ )
+ )
+ }
+
+ taskOrganizer.applyTransaction(wct)
+ }
+
+ override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? {
+ if (!autoTaskStackWindowing()) {
+ Slog.e(
+ TAG, "Failed to start transaction as the " +
+ "auto_task_stack_windowing TS flag is disabled."
+ )
+ return null
+ }
+ if (transaction.operations.isEmpty()) {
+ Slog.e(TAG, "Operations empty, no transaction started")
+ return null
+ }
+ if (DBG) Slog.d(TAG, "startTransaction ${transaction.operations}")
+
+ var wct = WindowContainerTransaction()
+ convertToWct(transaction, wct)
+ var pending = PendingTransition(
+ TRANSIT_CHANGE,
+ wct,
+ transaction,
+ )
+ return startTransitionNow(pending)
+ }
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo
+ ): WindowContainerTransaction? {
+ if (DBG) {
+ Slog.d(TAG, "handle request, id=${request.debugId}, type=${request.type}, " +
+ "triggertask = ${request.triggerTask ?: "null"}")
+ }
+ val ast = autoTransitionHandlerDelegate?.handleRequest(transition, request)
+ ?: run { return@handleRequest null }
+
+ if (ast.operations.isEmpty()) {
+ return null
+ }
+ var wct = WindowContainerTransaction()
+ convertToWct(ast, wct)
+
+ pendingTransitions.add(
+ PendingTransition(request.type, wct, ast).apply { isClaimed = transition }
+ )
+ return wct
+ }
+
+ fun updateTaskStackStates(taskStatStates: Map<Int, AutoTaskStackState>) {
+ taskStackStateMap.putAll(taskStatStates)
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: Transaction,
+ finishTransaction: Transaction,
+ finishCallback: TransitionFinishCallback
+ ): Boolean {
+ if (DBG) Slog.d(TAG, " startAnimation, id=${info.debugId} = changes=" + info.changes)
+ val pending: PendingTransition? = findPending(transition)
+ if (pending != null) {
+ pendingTransitions.remove(pending)
+ updateTaskStackStates(pending.transaction.getTaskStackStates())
+ }
+
+ reorderLeashes(startTransaction)
+ reorderLeashes(finishTransaction)
+
+ for (chg in info.changes) {
+ // TODO(b/384946072): handle the da stack similarly. The below implementation only
+ // handles the root task stack
+
+ val taskInfo = chg.taskInfo ?: continue
+ val taskStack = taskStackMap[taskInfo.taskId] ?: continue
+
+ // Restore the leashes for the task stacks to ensure correct z-order competition
+ if (taskStackMap.containsKey(taskInfo.taskId)) {
+ mTaskStackStateTranslator.restoreLeash(
+ taskStack,
+ startTransaction
+ )
+ if (TransitionUtil.isOpeningMode(chg.mode)) {
+ // Clients can still manipulate the alpha, but this ensures that the default
+ // behavior is natural
+ startTransaction.setAlpha(chg.leash, 1f)
+ }
+ continue
+ }
+ }
+
+ val isPlayedByDelegate = autoTransitionHandlerDelegate?.startAnimation(
+ transition,
+ pending?.transaction?.getTaskStackStates() ?: mapOf(),
+ info,
+ startTransaction,
+ finishTransaction,
+ {
+ shellMainThread.execute {
+ finishCallback.onTransitionFinished(it)
+ startNextTransition()
+ }
+ }
+ ) ?: false
+
+ if (isPlayedByDelegate) {
+ if (DBG) Slog.d(TAG, "${info.debugId} played");
+ return true;
+ }
+
+ // If for an animation which is not played by the delegate, contains a change in a known
+ // task stack, it should be leveraged to correct the leashes. So, handle the animation in
+ // this case.
+ if (info.changes.any { taskStackMap.containsKey(it.taskInfo?.taskId) }) {
+ startTransaction.apply()
+ finishCallback.onTransitionFinished(null)
+ startNextTransition()
+ if (DBG) Slog.d(TAG, "${info.debugId} played");
+ return true
+ }
+ return false;
+ }
+
+ fun convertToWct(ast: AutoTaskStackTransaction, wct: WindowContainerTransaction) {
+ ast.operations.forEach { operation ->
+ when (operation) {
+ is TaskStackOperation.ReparentTask -> {
+ val appTask = appTasksMap[operation.taskId]
+
+ if (appTask == null) {
+ Slog.e(
+ TAG, "task with id=$operation.taskId not found, failed to " +
+ "reparent."
+ )
+ return@forEach
+ }
+ if (!taskStackMap.containsKey(operation.parentTaskStackId)) {
+ Slog.e(
+ TAG, "task stack with id=${operation.parentTaskStackId} not " +
+ "found, failed to reparent"
+ )
+ return@forEach
+ }
+ // TODO(b/384946072): Handle a display area stack as well
+ wct.reparent(
+ appTask.token,
+ (taskStackMap[operation.parentTaskStackId] as RootTaskStack)
+ .rootTaskInfo.token,
+ operation.onTop
+ )
+ }
+
+ is TaskStackOperation.SendPendingIntent -> wct.sendPendingIntent(
+ operation.sender,
+ operation.intent,
+ operation.options
+ )
+
+ is TaskStackOperation.SetTaskStackState -> {
+ taskStackMap[operation.taskStackId]?.let { taskStack ->
+ mTaskStackStateTranslator.applyVisibilityAndBounds(
+ wct,
+ taskStack,
+ operation.state
+ )
+ }
+ ?: Slog.w(TAG, "AutoTaskStack with id ${operation.taskStackId} " +
+ "not found.")
+ }
+ }
+ }
+ }
+
+ override fun mergeAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ surfaceTransaction: Transaction,
+ mergeTarget: IBinder,
+ finishCallback: TransitionFinishCallback
+ ) {
+ val pending: PendingTransition? = findPending(transition)
+
+ autoTransitionHandlerDelegate?.mergeAnimation(
+ transition,
+ pending?.transaction?.getTaskStackStates() ?: mapOf(),
+ info,
+ surfaceTransaction,
+ mergeTarget,
+ /* finishCallback = */ {
+ shellMainThread.execute {
+ finishCallback.onTransitionFinished(it)
+ }
+ }
+ )
+ }
+
+ override fun onTransitionConsumed(
+ transition: IBinder,
+ aborted: Boolean,
+ finishTransaction: Transaction?
+ ) {
+ val pending: PendingTransition? = findPending(transition)
+ if (pending != null) {
+ pendingTransitions.remove(pending)
+ updateTaskStackStates(pending.transaction.getTaskStackStates())
+ // Still update the surface order because this means wm didn't lead to any change
+ if (finishTransaction != null) {
+ reorderLeashes(finishTransaction)
+ }
+ }
+ autoTransitionHandlerDelegate?.onTransitionConsumed(
+ transition,
+ pending?.transaction?.getTaskStackStates() ?: mapOf(),
+ aborted,
+ finishTransaction
+ )
+ }
+
+ private fun reorderLeashes(transaction: SurfaceControl.Transaction) {
+ taskStackStateMap.forEach { (taskId, taskStackState) ->
+ taskStackMap[taskId]?.let { taskStack ->
+ mTaskStackStateTranslator.reorderLeash(taskStack, taskStackState, transaction)
+ } ?: Slog.w(TAG, "Warning: AutoTaskStack with id $taskId not found.")
+ }
+ }
+
+ private fun findPending(claimed: IBinder) = pendingTransitions.find { it.isClaimed == claimed }
+
+ private fun startTransitionNow(pending: PendingTransition): IBinder {
+ val claimedTransition = transitions.startTransition(pending.mType, pending.wct, this)
+ pending.isClaimed = claimedTransition
+ pendingTransitions.add(pending)
+ return claimedTransition
+ }
+
+ fun startNextTransition() {
+ if (pendingTransitions.isEmpty()) return
+ val pending: PendingTransition = pendingTransitions[0]
+ if (pending.isClaimed != null) {
+ // Wait for this to start animating.
+ return
+ }
+ pending.isClaimed = transitions.startTransition(pending.mType, pending.wct, this)
+ }
+
+ internal class PendingTransition(
+ @field:WindowManager.TransitionType @param:WindowManager.TransitionType val mType: Int,
+ val wct: WindowContainerTransaction,
+ val transaction: AutoTaskStackTransaction,
+ ) {
+ var isClaimed: IBinder? = null
+ }
+
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt
new file mode 100644
index 000000000000..9d121b144492
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.automotive
+
+import com.android.wm.shell.ShellTaskOrganizer
+
+/**
+ * A [TaskListener] which simplifies the interface when used for
+ * [ShellTaskOrganizer.createRootTask].
+ *
+ * [onRootTaskStackCreated], [onRootTaskStackInfoChanged], [onRootTaskStackDestroyed] will be called
+ * for the underlying root task.
+ * The [onTaskAppeared], [onTaskInfoChanged], [onTaskVanished] are called for the children tasks.
+ */
+interface RootTaskStackListener : ShellTaskOrganizer.TaskListener {
+ fun onRootTaskStackCreated(rootTaskStack: RootTaskStack)
+ fun onRootTaskStackInfoChanged(rootTaskStack: RootTaskStack)
+ fun onRootTaskStackDestroyed(rootTaskStack: RootTaskStack)
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 4569cf31dab1..b9fccc1c4147 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -106,11 +106,12 @@ public class BackAnimationRunner {
private Runnable mFinishedCallback;
private RemoteAnimationTarget[] mApps;
- private IRemoteAnimationFinishedCallback mRemoteCallback;
+ private RemoteAnimationFinishedStub mRemoteCallback;
private static class RemoteAnimationFinishedStub extends IRemoteAnimationFinishedCallback.Stub {
//the binder callback should not hold strong reference to it to avoid memory leak.
- private WeakReference<BackAnimationRunner> mRunnerRef;
+ private final WeakReference<BackAnimationRunner> mRunnerRef;
+ private boolean mAbandoned;
private RemoteAnimationFinishedStub(BackAnimationRunner runner) {
mRunnerRef = new WeakReference<>(runner);
@@ -118,23 +119,29 @@ public class BackAnimationRunner {
@Override
public void onAnimationFinished() {
- BackAnimationRunner runner = mRunnerRef.get();
+ synchronized (this) {
+ if (mAbandoned) {
+ return;
+ }
+ }
+ final BackAnimationRunner runner = mRunnerRef.get();
if (runner == null) {
return;
}
- if (runner.shouldMonitorCUJ(runner.mApps)) {
- InteractionJankMonitor.getInstance().end(runner.mCujType);
- }
+ runner.onAnimationFinish(this);
+ }
- runner.mFinishedCallback.run();
- for (int i = runner.mApps.length - 1; i >= 0; --i) {
- SurfaceControl sc = runner.mApps[i].leash;
- if (sc != null && sc.isValid()) {
- sc.release();
- }
+ void abandon() {
+ synchronized (this) {
+ mAbandoned = true;
+ final BackAnimationRunner runner = mRunnerRef.get();
+ if (runner == null) {
+ return;
+ }
+ if (runner.shouldMonitorCUJ(runner.mApps)) {
+ InteractionJankMonitor.getInstance().end(runner.mCujType);
+ }
}
- runner.mApps = null;
- runner.mFinishedCallback = null;
}
}
@@ -144,13 +151,16 @@ public class BackAnimationRunner {
*/
void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
- InteractionJankMonitor interactionJankMonitor = InteractionJankMonitor.getInstance();
+ if (mRemoteCallback != null) {
+ mRemoteCallback.abandon();
+ mRemoteCallback = null;
+ }
+ mRemoteCallback = new RemoteAnimationFinishedStub(this);
mFinishedCallback = finishedCallback;
mApps = apps;
- if (mRemoteCallback == null) mRemoteCallback = new RemoteAnimationFinishedStub(this);
mWaitingAnimation = false;
if (shouldMonitorCUJ(apps)) {
- interactionJankMonitor.begin(
+ InteractionJankMonitor.getInstance().begin(
apps[0].leash, mContext, mHandler, mCujType);
}
try {
@@ -161,6 +171,28 @@ public class BackAnimationRunner {
}
}
+ void onAnimationFinish(RemoteAnimationFinishedStub finished) {
+ mHandler.post(() -> {
+ if (mRemoteCallback != null && finished != mRemoteCallback) {
+ return;
+ }
+ if (shouldMonitorCUJ(mApps)) {
+ InteractionJankMonitor.getInstance().end(mCujType);
+ }
+
+ mFinishedCallback.run();
+ for (int i = mApps.length - 1; i >= 0; --i) {
+ final SurfaceControl sc = mApps[i].leash;
+ if (sc != null && sc.isValid()) {
+ sc.release();
+ }
+ }
+ mApps = null;
+ mFinishedCallback = null;
+ mRemoteCallback = null;
+ });
+ }
+
@VisibleForTesting
boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
return apps.length > 0 && mCujType != NO_CUJ;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index b796b411dd1a..1323fe0fa9ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -60,7 +59,6 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import dagger.Lazy;
@@ -73,6 +71,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.IntPredicate;
import java.util.function.Predicate;
/**
@@ -198,9 +197,6 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final CompatUIStatusManager mCompatUIStatusManager;
@NonNull
- private final FocusTransitionObserver mFocusTransitionObserver;
-
- @NonNull
private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
public CompatUIController(@NonNull Context context,
@@ -217,8 +213,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
@NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
@NonNull AccessibilityManager accessibilityManager,
@NonNull CompatUIStatusManager compatUIStatusManager,
- @NonNull Optional<DesktopUserRepositories> desktopUserRepositories,
- @NonNull FocusTransitionObserver focusTransitionObserver) {
+ @NonNull Optional<DesktopUserRepositories> desktopUserRepositories) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -235,7 +230,6 @@ public class CompatUIController implements OnDisplaysChangedListener,
DISAPPEAR_DELAY_MS, flags);
mCompatUIStatusManager = compatUIStatusManager;
mDesktopUserRepositories = desktopUserRepositories;
- mFocusTransitionObserver = focusTransitionObserver;
shellInit.addInitCallback(this::onInit, this);
}
@@ -412,8 +406,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
// start tracking the buttons visibility for this task.
if (mTopActivityTaskId != taskInfo.taskId
&& !taskInfo.isTopActivityTransparent
- && taskInfo.isVisible
- && mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)) {
+ && taskInfo.isVisible && taskInfo.isFocused) {
mTopActivityTaskId = taskInfo.taskId;
setHasShownUserAspectRatioSettingsButton(false);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 2600bcc18f72..23a0f4adb6d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -272,8 +272,7 @@ public abstract class WMShellBaseModule {
@NonNull CompatUIState compatUIState,
@NonNull CompatUIComponentIdGenerator componentIdGenerator,
@NonNull CompatUIComponentFactory compatUIComponentFactory,
- CompatUIStatusManager compatUIStatusManager,
- @NonNull FocusTransitionObserver focusTransitionObserver) {
+ CompatUIStatusManager compatUIStatusManager) {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
@@ -298,8 +297,7 @@ public abstract class WMShellBaseModule {
compatUIShellCommandHandler.get(),
accessibilityManager.get(),
compatUIStatusManager,
- desktopUserRepositories,
- focusTransitionObserver));
+ desktopUserRepositories));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 636549fa0662..a6f8150ffc55 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -176,7 +176,6 @@ open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransitio
* transition
*/
@Ignore("TODO(b/356277166): enable the tablet test")
- @Postsubmit
@Test
open fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt
index 4987ab7b9344..d65f158e00d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt
@@ -78,7 +78,6 @@ class BottomHalfEnterPipToOtherOrientation(flicker: LegacyFlickerTest) :
}
@Ignore("TODO(b/356277166): enable the tablet test")
- @Presubmit
@Test
override fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
// Test app and pip app should covers the entire screen on start.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 784e1907894d..b5c9fa151dac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -62,11 +62,12 @@ import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import dagger.Lazy;
+import java.util.Optional;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -76,8 +77,6 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for {@link CompatUIController}.
*
@@ -128,8 +127,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
private DesktopUserRepositories mDesktopUserRepositories;
@Mock
private DesktopRepository mDesktopRepository;
- @Mock
- private FocusTransitionObserver mFocusTransitionObserver;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -165,8 +162,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
- mCompatUIStatusManager, Optional.of(mDesktopUserRepositories),
- mFocusTransitionObserver) {
+ mCompatUIStatusManager, Optional.of(mDesktopUserRepositories)) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -284,7 +280,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
doReturn(false).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
@@ -416,7 +411,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
// Verify button remains hidden while IME is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -449,7 +443,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
// Verify button remains hidden while keyguard is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -530,7 +523,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
@RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutRecreatedIfNeeded() {
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
doReturn(true).when(mMockRestartDialogLayout)
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
@@ -546,7 +538,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
@RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutNotRecreatedIfNotNeeded() {
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
doReturn(false).when(mMockRestartDialogLayout)
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
@@ -567,8 +558,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- /* isVisible */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+ /* isVisible */ true, /* isFocused */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo);
@@ -584,8 +574,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- /* isVisible */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+ /* isVisible */ true, /* isFocused */ true);
// Simulate task being shown
mController.updateActiveTaskInfo(taskInfo);
@@ -603,8 +592,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
// Create visible but NOT focused task
final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
- /* isVisible */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
+ /* isVisible */ true, /* isFocused */ false);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo1);
@@ -616,8 +604,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
// Create focused but NOT visible task
final TaskInfo taskInfo2 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
- /* isVisible */ false);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+ /* isVisible */ false, /* isFocused */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo2);
@@ -629,8 +616,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
// Create NOT focused but NOT visible task
final TaskInfo taskInfo3 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
- /* isVisible */ false);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
+ /* isVisible */ false, /* isFocused */ false);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo3);
@@ -646,8 +632,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
public void testUpdateActiveTaskInfo_sameTask_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- /* isVisible */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+ /* isVisible */ true, /* isFocused */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo);
@@ -675,8 +660,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
public void testUpdateActiveTaskInfo_transparentTask_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- /* isVisible */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+ /* isVisible */ true, /* isFocused */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo);
@@ -694,8 +678,7 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
// Create transparent task
final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
- /* isVisible */ true, /* isTopActivityTransparent */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+ /* isVisible */ true, /* isFocused */ true, /* isTopActivityTransparent */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo1);
@@ -711,7 +694,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(false);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
@@ -725,7 +707,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
@@ -744,7 +725,6 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() {
when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
- when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
@@ -759,22 +739,23 @@ public class CompatUIControllerTest extends CompatUIShellTestCase {
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
- /* isTopActivityTransparent */ false);
+ /* isFocused */ false, /* isTopActivityTransparent */ false);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
- boolean isVisible) {
+ boolean isVisible, boolean isFocused) {
return createTaskInfo(displayId, taskId, hasSizeCompat,
- isVisible, /* isTopActivityTransparent */ false);
+ isVisible, isFocused, /* isTopActivityTransparent */ false);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
- boolean isVisible, boolean isTopActivityTransparent) {
+ boolean isVisible, boolean isFocused, boolean isTopActivityTransparent) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.displayId = displayId;
taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.isVisible = isVisible;
+ taskInfo.isFocused = isFocused;
taskInfo.isTopActivityTransparent = isTopActivityTransparent;
taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(true);
taskInfo.appCompatTaskInfo.setTopActivityLetterboxed(true);
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
index 9f3c34575b94..81d9d81c4f58 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
@@ -74,6 +74,7 @@ public abstract class AppFunctionService extends Service {
/* context= */ this,
/* onExecuteFunction= */ (platformRequest,
callingPackage,
+ callingPackageSigningInfo,
cancellationSignal,
callback) -> {
AppFunctionService.this.onExecuteFunction(
@@ -105,15 +106,17 @@ public abstract class AppFunctionService extends Service {
/**
* Called by the system to execute a specific app function.
*
- * <p>This method is triggered when the system requests your AppFunctionService to handle a
- * particular function you have registered and made available.
+ * <p>This method is the entry point for handling all app function requests in an app. When the
+ * system needs your AppFunctionService to perform a function, it will invoke this method.
*
- * <p>To ensure proper routing of function requests, assign a unique identifier to each
- * function. This identifier doesn't need to be globally unique, but it must be unique within
- * your app. For example, a function to order food could be identified as "orderFood". In most
- * cases this identifier should come from the ID automatically generated by the AppFunctions
- * SDK. You can determine the specific function to invoke by calling {@link
- * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+ * <p>Each function you've registered is identified by a unique identifier. This identifier
+ * doesn't need to be globally unique, but it must be unique within your app. For example, a
+ * function to order food could be identified as "orderFood". In most cases, this identifier is
+ * automatically generated by the AppFunctions SDK.
+ *
+ * <p>You can determine which function to execute by calling {@link
+ * ExecuteAppFunctionRequest#getFunctionIdentifier()}. This allows your service to route the
+ * incoming request to the appropriate logic for handling the specific function.
*
* <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
* thread and dispatch the result with the given callback. You should always report back the
@@ -132,7 +135,5 @@ public abstract class AppFunctionService extends Service {
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull
- OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
- callback);
+ @NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback);
}
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 707577d6d075..c5095c1a0704 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -354,6 +354,7 @@ static jlongArray Typeface_readTypefaces(JNIEnv* env, jobject, jobject buffer, j
typeface->fStyle = minikin::FontStyle(&reader);
typeface->fAPIStyle = reader.read<Typeface::Style>();
typeface->fBaseWeight = reader.read<int>();
+ typeface->fIsVariationInstance = false;
faceHandles.push_back(toJLong(typeface));
}
const jlongArray result = env->NewLongArray(typefaceCount);
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index 859cc57dea9f..4c9656792dac 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -20,6 +20,7 @@
#include <private/performance_hint_private.h>
#include <future>
+#include <memory>
#include <optional>
#include <vector>
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index e52e0b16eca3..6a21496f1165 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -1,7 +1,10 @@
{
"presubmit": [
{
- "name": "CtsMediaBetterTogetherTestCases"
+ "name": "CtsMediaRouterTestCases"
+ },
+ {
+ "name": "CtsMediaSessionTestCases"
},
{
"name": "mediaroutertest"
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index afece5fac0fb..40a786ed560b 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -81,7 +81,9 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/device_list"
android:layout_width="match_parent"
- android:layout_height="200dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintHeight_max="220dp"
+ app:layout_constraintHeight_min="200dp"
android:scrollbars="vertical"
android:visibility="gone" />
diff --git a/packages/SettingsLib/DataStore/OWNERS b/packages/SettingsLib/DataStore/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/DataStore/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/Graph/OWNERS b/packages/SettingsLib/Graph/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Graph/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index 1ed814a2ae20..51813a1c9aab 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -69,7 +69,6 @@ constructor(
val visitedScreens: Set<String> = setOf(),
val locale: Locale? = null,
val flags: Int = PreferenceGetterFlags.ALL,
- val includeValue: Boolean = true, // TODO: clean up
val includeValueDescriptor: Boolean = true,
)
diff --git a/packages/SettingsLib/Ipc/OWNERS b/packages/SettingsLib/Ipc/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Ipc/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/Metadata/OWNERS b/packages/SettingsLib/Metadata/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Metadata/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/OWNERS_catalyst b/packages/SettingsLib/OWNERS_catalyst
new file mode 100644
index 000000000000..d44ac68585a2
--- /dev/null
+++ b/packages/SettingsLib/OWNERS_catalyst
@@ -0,0 +1,9 @@
+# OWNERS of Catalyst libraries (DataStore, Metadata, etc.)
+
+# Main developers
+jiannan@google.com
+cechkahn@google.com
+sunnyshao@google.com
+
+# Emergency only
+cipson@google.com
diff --git a/packages/SettingsLib/Preference/OWNERS b/packages/SettingsLib/Preference/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Preference/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/Service/OWNERS b/packages/SettingsLib/Service/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Service/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
index 7516d2e6ab1b..e3d7902f34b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
@@ -22,6 +22,7 @@ import static com.android.settingslib.enterprise.ActionDisabledLearnMoreButtonLa
import static com.android.settingslib.enterprise.ManagedDeviceActionDisabledByAdminController.DEFAULT_FOREGROUND_USER_CHECKER;
import android.app.admin.DevicePolicyManager;
+import android.app.supervision.SupervisionManager;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
@@ -59,12 +60,18 @@ public final class ActionDisabledByAdminControllerFactory {
}
private static boolean isSupervisedDevice(Context context) {
- DevicePolicyManager devicePolicyManager =
- context.getSystemService(DevicePolicyManager.class);
- ComponentName supervisionComponent =
- devicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
- new UserHandle(UserHandle.myUserId()));
- return supervisionComponent != null;
+ if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+ SupervisionManager supervisionManager =
+ context.getSystemService(SupervisionManager.class);
+ return supervisionManager.isSupervisionEnabledForUser(UserHandle.myUserId());
+ } else {
+ DevicePolicyManager devicePolicyManager =
+ context.getSystemService(DevicePolicyManager.class);
+ ComponentName supervisionComponent =
+ devicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ new UserHandle(UserHandle.myUserId()));
+ return supervisionComponent != null;
+ }
}
/**
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 4125a81f9bbc..fc61b1e875f3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -46,6 +46,7 @@ public class GlobalSettings {
Settings.Global.APP_AUTO_RESTRICTION_ENABLED,
Settings.Global.AUTO_TIME,
Settings.Global.AUTO_TIME_ZONE,
+ Settings.Global.TIME_ZONE_NOTIFICATIONS,
Settings.Global.POWER_SOUNDS_ENABLED,
Settings.Global.DOCK_SOUNDS_ENABLED,
Settings.Global.CHARGING_SOUNDS_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 1fc1f05ae149..dd28402d705f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -91,6 +91,7 @@ public class SecureSettings {
Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
Settings.Secure.KEY_REPEAT_DELAY_MS,
Settings.Secure.CAMERA_GESTURE_DISABLED,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 32d4580f67ec..c0e266fa269f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -102,6 +102,7 @@ public class GlobalSettingsValidators {
});
VALIDATORS.put(Global.AUTO_TIME, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.AUTO_TIME_ZONE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.TIME_ZONE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.DOCK_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index d0e88d5d6a3c..b01f6229af16 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -140,6 +140,8 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.KEY_REPEAT_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.KEY_REPEAT_DELAY_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index ef0bc3b100e0..c1c3e04d46fd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -212,10 +212,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private static final String ERROR_IO_EXCEPTION = "io_exception";
private static final String ERROR_FAILED_TO_RESTORE_SOFTAP_CONFIG =
"failed_to_restore_softap_config";
- private static final String ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES =
- "failed_to_convert_network_policies";
- private static final String ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION =
- "unknown_backup_serialization_version";
+ private static final String ERROR_FAILED_TO_RESTORE_WIFI_CONFIG =
+ "failed_to_restore_wifi_config";
// Name of the temporary file we use during full backup/restore. This is
@@ -1438,7 +1436,6 @@ public class SettingsBackupAgent extends BackupAgentHelper {
try {
out.writeInt(NETWORK_POLICIES_BACKUP_VERSION);
out.writeInt(policies.length);
- int numberOfPoliciesBackedUp = 0;
for (NetworkPolicy policy : policies) {
// We purposefully only backup policies that the user has
// defined; any inferred policies might include
@@ -1448,30 +1445,26 @@ public class SettingsBackupAgent extends BackupAgentHelper {
out.writeByte(BackupUtils.NOT_NULL);
out.writeInt(marshaledPolicy.length);
out.write(marshaledPolicy);
- if (areAgentMetricsEnabled) {
- numberOfPoliciesBackedUp++;
- }
} else {
out.writeByte(BackupUtils.NULL);
}
}
- if (areAgentMetricsEnabled) {
- numberOfSettingsPerKey.put(KEY_NETWORK_POLICIES, numberOfPoliciesBackedUp);
- }
} catch (IOException ioe) {
Log.e(TAG, "Failed to convert NetworkPolicies to byte array " + ioe.getMessage());
baos.reset();
- mBackupRestoreEventLogger.logItemsBackupFailed(
- KEY_NETWORK_POLICIES,
- policies.length,
- ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES);
}
}
return baos.toByteArray();
}
- private byte[] getNewWifiConfigData() {
- return mWifiManager.retrieveBackupData();
+ @VisibleForTesting
+ byte[] getNewWifiConfigData() {
+ byte[] data = mWifiManager.retrieveBackupData();
+ if (areAgentMetricsEnabled) {
+ // We're unable to determine how many settings this includes, so we'll just log 1.
+ numberOfSettingsPerKey.put(KEY_WIFI_NEW_CONFIG, 1);
+ }
+ return data;
}
private byte[] getLocaleSettings() {
@@ -1483,11 +1476,22 @@ public class SettingsBackupAgent extends BackupAgentHelper {
return localeList.toLanguageTags().getBytes();
}
- private void restoreNewWifiConfigData(byte[] bytes) {
+ @VisibleForTesting
+ void restoreNewWifiConfigData(byte[] bytes) {
if (DEBUG_BACKUP) {
Log.v(TAG, "Applying restored wifi data");
}
- mWifiManager.restoreBackupData(bytes);
+ if (areAgentMetricsEnabled) {
+ try {
+ mWifiManager.restoreBackupData(bytes);
+ mBackupRestoreEventLogger.logItemsRestored(KEY_WIFI_NEW_CONFIG, /* count= */ 1);
+ } catch (Exception e) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ KEY_WIFI_NEW_CONFIG, /* count= */ 1, ERROR_FAILED_TO_RESTORE_WIFI_CONFIG);
+ }
+ } else {
+ mWifiManager.restoreBackupData(bytes);
+ }
}
private void restoreNetworkPolicies(byte[] data) {
@@ -1498,10 +1502,6 @@ public class SettingsBackupAgent extends BackupAgentHelper {
try {
int version = in.readInt();
if (version < 1 || version > NETWORK_POLICIES_BACKUP_VERSION) {
- mBackupRestoreEventLogger.logItemsRestoreFailed(
- KEY_NETWORK_POLICIES,
- /* count= */ 1,
- ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION);
throw new BackupUtils.BadVersionException(
"Unknown Backup Serialization Version");
}
@@ -1518,15 +1518,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
// Only set the policies if there was no error in the restore operation
networkPolicyManager.setNetworkPolicies(policies);
- mBackupRestoreEventLogger.logItemsRestored(KEY_NETWORK_POLICIES, policies.length);
} catch (NullPointerException | IOException | BackupUtils.BadVersionException
| DateTimeException e) {
// NPE can be thrown when trying to instantiate a NetworkPolicy
Log.e(TAG, "Failed to convert byte array to NetworkPolicies " + e.getMessage());
- mBackupRestoreEventLogger.logItemsRestoreFailed(
- KEY_NETWORK_POLICIES,
- /* count= */ 1,
- ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES);
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index dedd7ebd1ef7..5ad4b8a6dffe 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1715,6 +1715,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
SecureSettingsProto.Accessibility.ENABLED_ACCESSIBILITY_SERVICES);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ SecureSettingsProto.Accessibility.AUTOCLICK_CURSOR_AREA_SIZE);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 95dd0db40c0e..6e5b602c02c5 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -16,6 +16,7 @@
package com.android.providers.settings;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG;
import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG;
import static junit.framework.Assert.assertEquals;
@@ -28,6 +29,8 @@ import static org.junit.Assert.assertArrayEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
@@ -69,6 +72,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -834,6 +838,74 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
assertNull(getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest));
}
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void getNewWifiConfigData_flagIsEnabled_numberOfSettingsInKeyAreRecorded() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+ when(mWifiManager.retrieveBackupData()).thenReturn(null);
+
+ mAgentUnderTest.getNewWifiConfigData();
+
+ assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_WIFI_NEW_CONFIG), 1);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void getNewWifiConfigData_flagIsNotEnabled_numberOfSettingsInKeyAreNotRecorded() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+ when(mWifiManager.retrieveBackupData()).thenReturn(null);
+
+ mAgentUnderTest.getNewWifiConfigData();
+
+ assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_WIFI_NEW_CONFIG), 0);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void
+ restoreNewWifiConfigData_flagIsEnabled_restoreIsSuccessful_successMetricsAreLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ doNothing().when(mWifiManager).restoreBackupData(any());
+
+ mAgentUnderTest.restoreNewWifiConfigData(new byte[] {});
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(KEY_WIFI_NEW_CONFIG, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getSuccessCount(), 1);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void
+ restoreNewWifiConfigData_flagIsEnabled_restoreIsNotSuccessful_failureMetricsAreLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ doThrow(new RuntimeException()).when(mWifiManager).restoreBackupData(any());
+
+ mAgentUnderTest.restoreNewWifiConfigData(new byte[] {});
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(KEY_WIFI_NEW_CONFIG, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreNewWifiConfigData_flagIsNotEnabled_metricsAreNotLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ doNothing().when(mWifiManager).restoreBackupData(any());
+
+ mAgentUnderTest.restoreNewWifiConfigData(new byte[] {});
+
+ assertNull(getLoggingResultForDatatype(KEY_WIFI_NEW_CONFIG, mAgentUnderTest));
+ }
+
private byte[] generateBackupData(Map<String, String> keyValueData) {
int totalBytes = 0;
for (String key : keyValueData.keySet()) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index 5f1f588bb2b5..c7d6e8aed3b4 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -34,6 +34,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
@@ -168,6 +169,12 @@ private class NestedDraggableNode(
CompositionLocalConsumerModifierNode,
OrientationAware {
private val nestedScrollDispatcher = NestedScrollDispatcher()
+ private var trackWheelScroll: SuspendingPointerInputModifierNode? = null
+ set(value) {
+ field?.let { undelegate(it) }
+ field = value?.also { delegate(it) }
+ }
+
private var trackDownPositionDelegate: SuspendingPointerInputModifierNode? = null
set(value) {
field?.let { undelegate(it) }
@@ -189,6 +196,7 @@ private class NestedDraggableNode(
* This is use to track the started position of a drag started on a nested scrollable.
*/
private var lastFirstDown: Offset? = null
+ private var lastEventWasScrollWheel: Boolean = false
/** The pointers currently down, in order of which they were done and mapping to their type. */
private val pointersDown = linkedMapOf<PointerId, PointerType>()
@@ -218,8 +226,11 @@ private class NestedDraggableNode(
nestedScrollController?.ensureOnDragStoppedIsCalled()
nestedScrollController = null
- if (!enabled && trackDownPositionDelegate != null) {
+ if (!enabled && trackWheelScroll != null) {
+ check(trackDownPositionDelegate != null)
check(detectDragsDelegate != null)
+
+ trackWheelScroll = null
trackDownPositionDelegate = null
detectDragsDelegate = null
}
@@ -232,17 +243,22 @@ private class NestedDraggableNode(
) {
if (!enabled) return
- if (trackDownPositionDelegate == null) {
+ if (trackWheelScroll == null) {
+ check(trackDownPositionDelegate == null)
check(detectDragsDelegate == null)
+
+ trackWheelScroll = SuspendingPointerInputModifierNode { trackWheelScroll() }
trackDownPositionDelegate = SuspendingPointerInputModifierNode { trackDownPosition() }
detectDragsDelegate = SuspendingPointerInputModifierNode { detectDrags() }
}
+ checkNotNull(trackWheelScroll).onPointerEvent(pointerEvent, pass, bounds)
checkNotNull(trackDownPositionDelegate).onPointerEvent(pointerEvent, pass, bounds)
checkNotNull(detectDragsDelegate).onPointerEvent(pointerEvent, pass, bounds)
}
override fun onCancelPointerInput() {
+ trackWheelScroll?.onCancelPointerInput()
trackDownPositionDelegate?.onCancelPointerInput()
detectDragsDelegate?.onCancelPointerInput()
}
@@ -457,6 +473,13 @@ private class NestedDraggableNode(
* ===============================
*/
+ private suspend fun PointerInputScope.trackWheelScroll() {
+ awaitEachGesture {
+ val event = awaitPointerEvent(pass = PointerEventPass.Initial)
+ lastEventWasScrollWheel = event.type == PointerEventType.Scroll
+ }
+ }
+
private suspend fun PointerInputScope.trackDownPosition() {
awaitEachGesture {
try {
@@ -501,7 +524,12 @@ private class NestedDraggableNode(
}
val sign = offset.sign
- if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) {
+ if (
+ nestedScrollController == null &&
+ // TODO(b/388231324): Remove this.
+ !lastEventWasScrollWheel &&
+ draggable.shouldConsumeNestedScroll(sign)
+ ) {
val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" }
// TODO(b/382665591): Ensure that there is at least one pointer down.
diff --git a/packages/SystemUI/compose/core/tests/AndroidManifest.xml b/packages/SystemUI/compose/core/tests/AndroidManifest.xml
index 28f80d4af265..7c721b97ee47 100644
--- a/packages/SystemUI/compose/core/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/core/tests/AndroidManifest.xml
@@ -15,6 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.compose.core.tests" >
<application>
@@ -23,7 +24,8 @@
<activity
android:name="androidx.activity.ComponentActivity"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
- android:exported="true" />
+ android:exported="true"
+ tools:replace="android:theme" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index 7f70e97411f4..f9cf495d9d9f 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -18,6 +18,8 @@ package com.android.compose.gesture
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -35,6 +37,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.ScrollWheel
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
@@ -710,6 +713,33 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
rule.onRoot().performTouchInput { down(center) }
}
+ @Test
+ // TODO(b/388231324): Remove this.
+ fun nestedScrollWithMouseWheelIsIgnored() {
+ val draggable = TestDraggable()
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(draggable, orientation)
+ .scrollable(rememberScrollableState { 0f }, orientation)
+ )
+ }
+
+ rule.onRoot().performMouseInput {
+ enter(center)
+ scroll(
+ touchSlop + 1f,
+ when (orientation) {
+ Orientation.Horizontal -> ScrollWheel.Horizontal
+ Orientation.Vertical -> ScrollWheel.Vertical
+ },
+ )
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+ }
+
private fun ComposeContentTestRule.setContentWithTouchSlop(
content: @Composable () -> Unit
): Float {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 6bb579d18bf9..b41c55858c75 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -60,17 +60,12 @@ internal interface DragController {
* Stop the current drag with the given [velocity].
*
* @param velocity The velocity of the drag when it stopped.
- * @param canChangeContent Whether the content can be changed as a result of this drag.
* @return the consumed [velocity] when the animation complete
*/
- suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float
+ suspend fun onStop(velocity: Float): Float
- /**
- * Cancels the current drag.
- *
- * @param canChangeContent Whether the content can be changed as a result of this drag.
- */
- fun onCancel(canChangeContent: Boolean)
+ /** Cancels the current drag. */
+ fun onCancel()
}
internal class DraggableHandlerImpl(
@@ -295,17 +290,16 @@ private class DragControllerImpl(
return newOffset - previousOffset
}
- override suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float {
+ override suspend fun onStop(velocity: Float): Float {
// To ensure that any ongoing animation completes gracefully and avoids an undefined state,
// we execute the actual `onStop` logic in a non-cancellable context. This prevents the
// coroutine from being cancelled prematurely, which could interrupt the animation.
// TODO(b/378470603) Remove this check once NestedDraggable is used to handle drags.
- return withContext(NonCancellable) { onStop(velocity, canChangeContent, swipeAnimation) }
+ return withContext(NonCancellable) { onStop(velocity, swipeAnimation) }
}
private suspend fun <T : ContentKey> onStop(
velocity: Float,
- canChangeContent: Boolean,
// Important: Make sure that this has the same name as [this.swipeAnimation] so that all the
// code here references the current animation when [onDragStopped] is called, otherwise the
@@ -319,35 +313,27 @@ private class DragControllerImpl(
}
val fromContent = swipeAnimation.fromContent
+ // If we are halfway between two contents, we check what the target will be based on
+ // the velocity and offset of the transition, then we launch the animation.
+
+ val toContent = swipeAnimation.toContent
+
+ // Compute the destination content (and therefore offset) to settle in.
+ val offset = swipeAnimation.dragOffset
+ val distance = swipeAnimation.distance()
val targetContent =
- if (canChangeContent) {
- // If we are halfway between two contents, we check what the target will be based on
- // the velocity and offset of the transition, then we launch the animation.
-
- val toContent = swipeAnimation.toContent
-
- // Compute the destination content (and therefore offset) to settle in.
- val offset = swipeAnimation.dragOffset
- val distance = swipeAnimation.distance()
- if (
- distance != DistanceUnspecified &&
- shouldCommitSwipe(
- offset = offset,
- distance = distance,
- velocity = velocity,
- wasCommitted = swipeAnimation.currentContent == toContent,
- requiresFullDistanceSwipe = swipeAnimation.requiresFullDistanceSwipe,
- )
- ) {
- toContent
- } else {
- fromContent
- }
+ if (
+ distance != DistanceUnspecified &&
+ shouldCommitSwipe(
+ offset = offset,
+ distance = distance,
+ velocity = velocity,
+ wasCommitted = swipeAnimation.currentContent == toContent,
+ requiresFullDistanceSwipe = swipeAnimation.requiresFullDistanceSwipe,
+ )
+ ) {
+ toContent
} else {
- // We are doing an overscroll preview animation between scenes.
- check(fromContent == swipeAnimation.currentContent) {
- "canChangeContent is false but currentContent != fromContent"
- }
fromContent
}
@@ -423,10 +409,8 @@ private class DragControllerImpl(
}
}
- override fun onCancel(canChangeContent: Boolean) {
- swipeAnimation.contentTransition.coroutineScope.launch {
- onStop(velocity = 0f, canChangeContent = canChangeContent)
- }
+ override fun onCancel() {
+ swipeAnimation.contentTransition.coroutineScope.launch { onStop(velocity = 0f) }
}
}
@@ -519,11 +503,11 @@ private fun scrollController(
}
override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
- return dragController.onStop(velocity = initialVelocity, canChangeContent = true)
+ return dragController.onStop(velocity = initialVelocity)
}
override fun onCancel() {
- dragController.onCancel(canChangeContent = true)
+ dragController.onCancel()
}
/**
@@ -547,9 +531,9 @@ internal const val OffsetVisibilityThreshold = 0.5f
private object NoOpDragController : DragController {
override fun onDrag(delta: Float) = 0f
- override suspend fun onStop(velocity: Float, canChangeContent: Boolean) = 0f
+ override suspend fun onStop(velocity: Float) = 0f
- override fun onCancel(canChangeContent: Boolean) {
+ override fun onCancel() {
/* do nothing */
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index f5f01d4d1a35..89320f1303e5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -307,13 +307,13 @@ internal class MultiPointerDraggableNode(
velocityTracker.calculateVelocity(maxVelocity)
}
.toFloat(),
- onFling = { controller.onStop(it, canChangeContent = true) },
+ onFling = { controller.onStop(it) },
)
},
onDragCancel = { controller ->
startFlingGesture(
initialVelocity = 0f,
- onFling = { controller.onStop(it, canChangeContent = true) },
+ onFling = { controller.onStop(it) },
)
},
swipeDetector = swipeDetector,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 5a35d11c0b29..dbac62ffb713 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -247,32 +247,26 @@ class DraggableHandlerTest {
suspend fun DragController.onDragStoppedAnimateNow(
velocity: Float,
- canChangeScene: Boolean = true,
onAnimationStart: () -> Unit,
onAnimationEnd: (Float) -> Unit,
) {
- val velocityConsumed = onDragStoppedAnimateLater(velocity, canChangeScene)
+ val velocityConsumed = onDragStoppedAnimateLater(velocity)
onAnimationStart()
onAnimationEnd(velocityConsumed.await())
}
suspend fun DragController.onDragStoppedAnimateNow(
velocity: Float,
- canChangeScene: Boolean = true,
onAnimationStart: () -> Unit,
) =
onDragStoppedAnimateNow(
velocity = velocity,
- canChangeScene = canChangeScene,
onAnimationStart = onAnimationStart,
onAnimationEnd = {},
)
- fun DragController.onDragStoppedAnimateLater(
- velocity: Float,
- canChangeScene: Boolean = true,
- ): Deferred<Float> {
- val velocityConsumed = testScope.async { onStop(velocity, canChangeScene) }
+ fun DragController.onDragStoppedAnimateLater(velocity: Float): Deferred<Float> {
+ val velocityConsumed = testScope.async { onStop(velocity) }
testScope.testScheduler.runCurrent()
return velocityConsumed
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 4153350fce60..5c6f91bb0e90 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -72,12 +72,12 @@ class MultiPointerDraggableTest {
return delta
}
- override suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float {
+ override suspend fun onStop(velocity: Float): Float {
onStop.invoke(velocity)
return velocity
}
- override fun onCancel(canChangeContent: Boolean) {
+ override fun onCancel() {
error("MultiPointerDraggable never calls onCancel()")
}
}
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 7c88d76f28bd..183e4d6f624b 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
@@ -24,6 +24,7 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
import android.os.SystemClock
import android.view.KeyEvent
import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.ACTION_UP
import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
@@ -540,11 +541,7 @@ object TestShortcuts {
simpleShortcutCategory(System, "System apps", "Take a note"),
simpleShortcutCategory(System, "System controls", "Take screenshot"),
simpleShortcutCategory(System, "System controls", "Go back"),
- simpleShortcutCategory(
- MultiTasking,
- "Split screen",
- "Switch to full screen",
- ),
+ simpleShortcutCategory(MultiTasking, "Split screen", "Switch to full screen"),
simpleShortcutCategory(
MultiTasking,
"Split screen",
@@ -704,7 +701,7 @@ object TestShortcuts {
android.view.KeyEvent(
/* downTime = */ SystemClock.uptimeMillis(),
/* eventTime = */ SystemClock.uptimeMillis(),
- /* action = */ ACTION_DOWN,
+ /* action = */ ACTION_UP,
/* code = */ KEYCODE_A,
/* repeat = */ 0,
/* metaState = */ 0,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index 755c218f6789..d9d34f5ace7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -92,13 +92,14 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- assertThat(uiState).isEqualTo(
- AddShortcutDialog(
- shortcutLabel = "Standard shortcut",
- defaultCustomShortcutModifierKey =
- ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+ assertThat(uiState)
+ .isEqualTo(
+ AddShortcutDialog(
+ shortcutLabel = "Standard shortcut",
+ defaultCustomShortcutModifierKey =
+ ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+ )
)
- )
}
}
@@ -137,8 +138,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- assertThat((uiState as AddShortcutDialog).pressedKeys)
- .isEmpty()
+ assertThat((uiState as AddShortcutDialog).pressedKeys).isEmpty()
}
}
@@ -161,8 +161,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
- assertThat((uiState as AddShortcutDialog).errorMessage)
- .isEmpty()
+ assertThat((uiState as AddShortcutDialog).errorMessage).isEmpty()
}
}
@@ -244,32 +243,34 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
@Test
- fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
+ fun onShortcutKeyCombinationSelected_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
testScope.runTest {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- val isHandled = viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
+ val isHandled =
+ viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
assertThat(isHandled).isTrue()
}
}
@Test
- fun onKeyPressed_doesNotHandleKeyEvents_whenActionKeyIsNotAlsoPressed() {
+ fun onShortcutKeyCombinationSelected_doesNotHandleKeyEvents_whenActionKeyIsNotAlsoPressed() {
testScope.runTest {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- val isHandled = viewModel.onKeyPressed(keyDownEventWithoutActionKeyPressed)
+ val isHandled =
+ viewModel.onShortcutKeyCombinationSelected(keyDownEventWithoutActionKeyPressed)
assertThat(isHandled).isFalse()
}
}
@Test
- fun onKeyPressed_convertsKeyEventsAndUpdatesUiStatesPressedKey() {
+ fun onShortcutKeyCombinationSelected_convertsKeyEventsAndUpdatesUiStatesPressedKey() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
- viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+ viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
+ viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
// Note that Action Key is excluded as it's already displayed on the UI
assertThat((uiState as AddShortcutDialog).pressedKeys)
@@ -282,8 +283,8 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
- viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+ viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
+ viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
// Note that Action Key is excluded as it's already displayed on the UI
assertThat((uiState as AddShortcutDialog).pressedKeys)
@@ -292,16 +293,15 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
// Close the dialog and show it again
viewModel.onDialogDismissed()
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- assertThat((uiState as AddShortcutDialog).pressedKeys)
- .isEmpty()
+ assertThat((uiState as AddShortcutDialog).pressedKeys).isEmpty()
}
}
private suspend fun openAddShortcutDialogAndSetShortcut() {
viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
- viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
- viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+ viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
+ viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
viewModel.onSetShortcut()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index b3417b9de36d..c44f27ef348b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -46,8 +46,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -76,7 +74,6 @@ class KeyguardInteractorTest : SysuiTestCase() {
private val configRepository by lazy { kosmos.fakeConfigurationRepository }
private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
private val shadeRepository by lazy { kosmos.shadeRepository }
- private val powerInteractor by lazy { kosmos.powerInteractor }
private val keyguardRepository by lazy { kosmos.keyguardRepository }
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
@@ -444,7 +441,6 @@ class KeyguardInteractorTest : SysuiTestCase() {
repository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
- powerInteractor.setAwakeForTest()
advanceTimeBy(1000L)
assertThat(isAbleToDream).isEqualTo(false)
@@ -460,9 +456,6 @@ class KeyguardInteractorTest : SysuiTestCase() {
repository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
- powerInteractor.setAwakeForTest()
- runCurrent()
-
// After some delay, still false
advanceTimeBy(300L)
assertThat(isAbleToDream).isEqualTo(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
index b0698555941c..98e3c68e6e33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -406,4 +407,48 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
// It should not have any effect.
assertEquals(listOf(false, true, false, true), canWake)
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testCanWakeDirectlyToGone_falseAsSoonAsTransitionsAwayFromGone() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false // Defaults to false.
+ ),
+ canWake,
+ )
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ assertEquals(
+ listOf(
+ false,
+ true, // Because we're GONE.
+ ),
+ canWake,
+ )
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // False as soon as we start a transition away from GONE.
+ ),
+ canWake,
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
index feaf06aca29a..ade7614ae853 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,10 +16,13 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -72,6 +75,28 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase()
}
@Test
+ @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+ @BrokenWithSceneContainer(388068805)
+ fun notifications_areFullyVisible_whenShadeIsOpen() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationAlpha)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ values.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
+ @Test
fun blurRadiusGoesToMaximumWhenShadeIsExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
@@ -88,6 +113,25 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase()
}
@Test
+ @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+ @BrokenWithSceneContainer(388068805)
+ fun notificationBlur_isNonZero_whenShadeIsExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationBlurRadius)
+
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
+ startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+ endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+ transitionFactory = ::step,
+ actualValuesProvider = { values },
+ checkInterpolatedValues = false,
+ )
+ }
+
+ @Test
fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index d909c5ab5f1b..914094fa39df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,9 +16,11 @@
package com.android.systemui.keyguard.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_BOUNCER_UI_REVAMP
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -153,7 +155,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
}
@Test
- @BrokenWithSceneContainer(330311871)
+ @BrokenWithSceneContainer(388068805)
fun blurRadiusIsMaxWhenShadeIsExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
@@ -170,7 +172,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
}
@Test
- @BrokenWithSceneContainer(330311871)
+ @BrokenWithSceneContainer(388068805)
fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.windowBlurRadius)
@@ -185,6 +187,44 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
)
}
+ @Test
+ @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+ @BrokenWithSceneContainer(388068805)
+ fun notificationBlur_isNonZero_whenShadeIsExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationBlurRadius)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+ runCurrent()
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
+ startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+ endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+ transitionFactory = ::step,
+ actualValuesProvider = { values },
+ checkInterpolatedValues = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+ @BrokenWithSceneContainer(388068805)
+ fun notifications_areFullyVisible_whenShadeIsExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationAlpha)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+ runCurrent()
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
+ startValue = 1.0f,
+ endValue = 1.0f,
+ transitionFactory = ::step,
+ actualValuesProvider = { values },
+ checkInterpolatedValues = false,
+ )
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index fc1d73b62abd..3a3f5371d195 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.when;
import android.app.Dialog;
import android.media.projection.StopReason;
import android.os.Handler;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.service.quicksettings.Tile;
import android.testing.TestableLooper;
@@ -52,6 +53,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsDetailedView;
import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
@@ -63,6 +65,7 @@ import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,11 +73,11 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.List;
-
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
+import java.util.List;
+
@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -82,7 +85,8 @@ public class ScreenRecordTileTest extends SysuiTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+ return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX,
+ QsDetailedView.FLAG_NAME);
}
@Mock
@@ -336,6 +340,30 @@ public class ScreenRecordTileTest extends SysuiTestCase {
.notifyPermissionRequestDisplayed(mContext.getUserId());
}
+ @Test
+ @EnableFlags(QsDetailedView.FLAG_NAME)
+ public void testNotStartingAndRecording_returnDetailsViewModel() {
+ when(mController.isStarting()).thenReturn(false);
+ when(mController.isRecording()).thenReturn(false);
+ mTile.getDetailsViewModel(Assert::assertNotNull);
+ }
+
+ @Test
+ @EnableFlags(QsDetailedView.FLAG_NAME)
+ public void testStarting_notReturnDetailsViewModel() {
+ when(mController.isStarting()).thenReturn(true);
+ when(mController.isRecording()).thenReturn(false);
+ mTile.getDetailsViewModel(Assert::assertNull);
+ }
+
+ @Test
+ @EnableFlags(QsDetailedView.FLAG_NAME)
+ public void testRecording_notReturnDetailsViewModel() {
+ when(mController.isStarting()).thenReturn(false);
+ when(mController.isRecording()).thenReturn(true);
+ mTile.getDetailsViewModel(Assert::assertNull);
+ }
+
private QSTile.Icon createExpectedIcon(int resId) {
if (QsInCompose.isEnabled()) {
return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 789ca5158dbf..62c360400582 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -85,7 +85,6 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -335,16 +334,14 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
mMainDispatcher = getMainDispatcher();
- KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
- KeyguardInteractorFactory.create();
- mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
+ mFakeKeyguardRepository = mKosmos.getKeyguardRepository();
mFakeKeyguardClockRepository = new FakeKeyguardClockRepository();
mKeyguardClockInteractor = mKosmos.getKeyguardClockInteractor();
- mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
+ mKeyguardInteractor = mKosmos.getKeyguardInteractor();
mShadeRepository = new FakeShadeRepository();
mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl(
new ShadeAnimationRepository(), mShadeRepository);
- mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
+ mPowerInteractor = mKosmos.getPowerInteractor();
when(mKeyguardTransitionInteractor.isInTransitionWhere(any(), any())).thenReturn(
MutableStateFlow(false));
when(mKeyguardTransitionInteractor.isInTransition(any(), any()))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
index bb9141afe404..5f73ac45d12f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
@@ -45,8 +45,11 @@ import kotlin.test.Test
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.verifyNoMoreInteractions
@SmallTest
@@ -106,10 +109,10 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
// Make sure the legacy code path does not change airplane mode when the refactor
// flag is enabled.
underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotAirplane), any())
underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotAirplane), any())
}
@Test
@@ -144,10 +147,10 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
// Make sure changing airplane mode from airplaneModeRepository does nothing
// if the StatusBarSignalPolicyRefactor is not enabled.
airplaneModeInteractor.setIsAirplaneMode(true)
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotAirplane), any())
airplaneModeInteractor.setIsAirplaneMode(false)
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotAirplane), any())
}
@Test
@@ -196,7 +199,7 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
underTest.setEthernetIndicators(
IconState(/* visible= */ true, /* icon= */ 1, /* contentDescription= */ "Ethernet")
)
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
underTest.setEthernetIndicators(
IconState(
@@ -205,7 +208,7 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
/* contentDescription= */ "No ethernet",
)
)
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
}
@Test
@@ -217,13 +220,13 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
clearInvocations(statusBarIconController)
connectivityRepository.fake.setEthernetConnected(default = true, validated = true)
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
connectivityRepository.fake.setEthernetConnected(default = false, validated = false)
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
connectivityRepository.fake.setEthernetConnected(default = true, validated = false)
- verifyNoMoreInteractions(statusBarIconController)
+ verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
index 912633c874ed..e6fbc725af04 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
@@ -101,7 +101,6 @@ class RenderStageManagerTest : SysuiTestCase() {
// VERIFY that the renderer is not queried for group or row controllers
inOrder(spyViewRenderer).apply {
verify(spyViewRenderer, times(1)).onRenderList(any())
- verify(spyViewRenderer, times(1)).getStackController()
verify(spyViewRenderer, never()).getGroupController(any())
verify(spyViewRenderer, never()).getRowController(any())
verify(spyViewRenderer, times(1)).onDispatchComplete()
@@ -121,7 +120,6 @@ class RenderStageManagerTest : SysuiTestCase() {
// VERIFY that the renderer is queried once per group/entry
inOrder(spyViewRenderer).apply {
verify(spyViewRenderer, times(1)).onRenderList(any())
- verify(spyViewRenderer, times(1)).getStackController()
verify(spyViewRenderer, times(2)).getGroupController(any())
verify(spyViewRenderer, times(8)).getRowController(any())
verify(spyViewRenderer, times(1)).onDispatchComplete()
@@ -144,7 +142,6 @@ class RenderStageManagerTest : SysuiTestCase() {
// VERIFY that the renderer is queried once per group/entry
inOrder(spyViewRenderer).apply {
verify(spyViewRenderer, times(1)).onRenderList(any())
- verify(spyViewRenderer, times(1)).getStackController()
verify(spyViewRenderer, times(2)).getGroupController(any())
verify(spyViewRenderer, times(8)).getRowController(any())
verify(spyViewRenderer, times(1)).onDispatchComplete()
@@ -162,7 +159,7 @@ class RenderStageManagerTest : SysuiTestCase() {
onRenderListListener.onRenderList(listWith2Groups8Entries())
// VERIFY that the listeners are invoked once per group and once per entry
- verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any())
+ verify(onAfterRenderListListener, times(1)).onAfterRenderList(any())
verify(onAfterRenderGroupListener, times(2)).onAfterRenderGroup(any(), any())
verify(onAfterRenderEntryListener, times(8)).onAfterRenderEntry(any(), any())
verifyNoMoreInteractions(
@@ -182,7 +179,7 @@ class RenderStageManagerTest : SysuiTestCase() {
onRenderListListener.onRenderList(listOf())
// VERIFY that the stack listener is invoked once but other listeners are not
- verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any())
+ verify(onAfterRenderListListener, times(1)).onAfterRenderList(any())
verify(onAfterRenderGroupListener, never()).onAfterRenderGroup(any(), any())
verify(onAfterRenderEntryListener, never()).onAfterRenderEntry(any(), any())
verifyNoMoreInteractions(
@@ -203,8 +200,6 @@ class RenderStageManagerTest : SysuiTestCase() {
private class FakeNotifViewRenderer : NotifViewRenderer {
override fun onRenderList(notifList: List<ListEntry>) {}
- override fun getStackController(): NotifStackController = mock()
-
override fun getGroupController(group: GroupEntry): NotifGroupController = mock()
override fun getRowController(entry: NotificationEntry): NotifRowController = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 54ce88b40c11..83c61507a506 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -26,7 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -275,7 +275,6 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = true,
hasNonClearableSilentNotifs = false,
@@ -293,7 +292,6 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = false,
@@ -311,7 +309,6 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 0,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = false,
@@ -329,7 +326,6 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = false,
@@ -347,7 +343,6 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = true,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = true,
@@ -365,7 +360,6 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = true,
hasNonClearableSilentNotifs = false,
@@ -383,7 +377,6 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = true,
@@ -401,7 +394,6 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 06b1c432955a..b3a60b052d08 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -37,7 +37,7 @@ import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter
@@ -115,7 +115,6 @@ class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = true,
hasNonClearableSilentNotifs = false,
@@ -133,7 +132,6 @@ class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = false,
@@ -151,7 +149,6 @@ class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = true,
hasNonClearableSilentNotifs = false,
@@ -183,7 +180,6 @@ class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
// AND there are clearable notifications
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = true,
hasNonClearableSilentNotifs = false,
@@ -217,7 +213,6 @@ class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
// AND there are clearable notifications
activeNotificationListRepository.notifStats.value =
NotifStats(
- numActiveNotifs = 2,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = true,
hasNonClearableSilentNotifs = false,
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 459778868ccd..a045b37a8119 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
@@ -70,6 +70,7 @@ import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel.HorizontalPosition
import com.android.systemui.testKosmos
+import com.android.systemui.window.ui.viewmodel.fakeBouncerTransitions
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertIs
@@ -1395,6 +1396,19 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
assertThat(stackAbsoluteBottom).isEqualTo(100F)
}
+ @Test
+ fun blurRadius_emitsValues_fromPrimaryBouncerTransitions() =
+ testScope.runTest {
+ val blurRadius by collectLastValue(underTest.blurRadius)
+ assertThat(blurRadius).isEqualTo(0.0f)
+
+ kosmos.fakeBouncerTransitions.first().notificationBlurRadius.value = 30.0f
+ assertThat(blurRadius).isEqualTo(30.0f)
+
+ kosmos.fakeBouncerTransitions.last().notificationBlurRadius.value = 40.0f
+ assertThat(blurRadius).isEqualTo(40.0f)
+ }
+
private suspend fun TestScope.showLockscreen() {
shadeTestUtil.setQsExpansion(0f)
shadeTestUtil.setLockscreenShadeExpansion(0f)
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 a9db0b70dd4d..faf736a543dd 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
@@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
class FakeHomeStatusBarViewModel(
override val operatorNameViewModel: StatusBarOperatorNameViewModel
) : HomeStatusBarViewModel {
- private val areNotificationLightsOut = MutableStateFlow(false)
+ override val areNotificationsLightsOut = MutableStateFlow(false)
override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
@@ -77,14 +77,12 @@ class FakeHomeStatusBarViewModel(
override val iconBlockList: MutableStateFlow<List<String>> = MutableStateFlow(listOf())
- override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut
-
val darkRegions = mutableListOf<Rect>()
var darkIconTint = Color.BLACK
var lightIconTint = Color.WHITE
- override fun areaTint(displayId: Int): Flow<StatusBarTintColor> =
+ override val areaTint: Flow<StatusBarTintColor> =
MutableStateFlow(
StatusBarTintColor { viewBounds ->
if (DarkIconDispatcher.isInAreas(darkRegions, viewBounds)) {
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 e91875cd0885..a70b777a25f2 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
@@ -22,6 +22,7 @@ import android.app.StatusBarManager.DISABLE_CLOCK
import android.app.StatusBarManager.DISABLE_NONE
import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import android.content.testableContext
import android.graphics.Rect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -59,7 +60,6 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
import com.android.systemui.statusbar.data.model.StatusBarMode
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
@@ -363,7 +363,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(testNotifications)
- val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+ val actual by collectLastValue(underTest.areNotificationsLightsOut)
assertThat(actual).isTrue()
}
@@ -377,7 +377,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(emptyList())
- val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+ val actual by collectLastValue(underTest.areNotificationsLightsOut)
assertThat(actual).isFalse()
}
@@ -391,7 +391,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(emptyList())
- val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+ val actual by collectLastValue(underTest.areNotificationsLightsOut)
assertThat(actual).isFalse()
}
@@ -405,7 +405,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(testNotifications)
- val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+ val actual by collectLastValue(underTest.areNotificationsLightsOut)
assertThat(actual).isFalse()
}
@@ -415,7 +415,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
fun areNotificationsLightsOut_requiresFlagEnabled() =
kosmos.runTest {
assertLogsWtf {
- val flow = underTest.areNotificationsLightsOut(DISPLAY_ID)
+ val flow = underTest.areNotificationsLightsOut
assertThat(flow).isEqualTo(emptyFlow<Boolean>())
}
}
@@ -1005,11 +1005,11 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun areaTint_viewIsInDarkBounds_getsDarkTint() =
kosmos.runTest {
- val displayId = 321
+ val displayId = testableContext.displayId
fakeDarkIconRepository.darkState(displayId).value =
SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
- val areaTint by collectLastValue(underTest.areaTint(displayId))
+ val areaTint by collectLastValue(underTest.areaTint)
val tint = areaTint?.tint(Rect(1, 1, 3, 3))
@@ -1019,11 +1019,11 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun areaTint_viewIsNotInDarkBounds_getsDefaultTint() =
kosmos.runTest {
- val displayId = 321
+ val displayId = testableContext.displayId
fakeDarkIconRepository.darkState(displayId).value =
SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
- val areaTint by collectLastValue(underTest.areaTint(displayId))
+ val areaTint by collectLastValue(underTest.areaTint)
val tint = areaTint?.tint(Rect(6, 6, 7, 7))
@@ -1033,11 +1033,11 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun areaTint_viewIsInDarkBounds_darkBoundsChange_viewUpdates() =
kosmos.runTest {
- val displayId = 321
+ val displayId = testableContext.displayId
fakeDarkIconRepository.darkState(displayId).value =
SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
- val areaTint by collectLastValue(underTest.areaTint(displayId))
+ val areaTint by collectLastValue(underTest.areaTint)
var tint = areaTint?.tint(Rect(1, 1, 3, 3))
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 162d8aebfc62..02b2bcf8e40d 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -1,5 +1,11 @@
-include proguard_kotlin.flags
--keep class com.android.systemui.VendorServices
+
+# VendorServices implements CoreStartable and may be instantiated reflectively in
+# SystemUIApplication#startAdditionalStartable.
+# TODO(b/373579455): Rewrite this to a @UsesReflection keep annotation.
+-keep class com.android.systemui.VendorServices {
+ public void <init>();
+}
# Needed to ensure callback field references are kept in their respective
# owning classes when the downstream callback registrars only store weak refs.
diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml
new file mode 100644
index 000000000000..f8c0fa04cd39
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml
@@ -0,0 +1,199 @@
+<?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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="scaleX"
+ android:startOffset="1000"
+ android:valueFrom="0.45561"
+ android:valueTo="0.69699"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="scaleY"
+ android:startOffset="1000"
+ android:valueFrom="0.6288400000000001"
+ android:valueTo="0.81618"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="scaleX"
+ android:startOffset="1083"
+ android:valueFrom="0.69699"
+ android:valueTo="1.05905"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="scaleY"
+ android:startOffset="1083"
+ android:valueFrom="0.81618"
+ android:valueTo="1.0972"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="90"
+ android:valueTo="135"
+ 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="500"
+ android:propertyName="rotation"
+ android:startOffset="500"
+ android:valueFrom="135"
+ android:valueTo="180"
+ 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_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="scaleX"
+ android:startOffset="1000"
+ android:valueFrom="0.0434"
+ android:valueTo="0.05063"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="scaleY"
+ android:startOffset="1000"
+ android:valueFrom="0.0434"
+ android:valueTo="0.042350000000000006"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="scaleX"
+ android:startOffset="1083"
+ android:valueFrom="0.05063"
+ android:valueTo="0.06146"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="scaleY"
+ android:startOffset="1083"
+ android:valueFrom="0.042350000000000006"
+ android:valueTo="0.040780000000000004"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,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="1017"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="88dp"
+ android:height="56dp"
+ android:viewportHeight="56"
+ android:viewportWidth="88">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="0.493"
+ android:pivotY="0.124"
+ android:scaleX="1.05905"
+ android:scaleY="1.0972"
+ android:translateX="43.528999999999996"
+ android:translateY="27.898">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#3d90ff"
+ android:fillType="nonZero"
+ android:pathData=" M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:rotation="0"
+ android:scaleX="0.06146"
+ android:scaleY="0.040780000000000004"
+ android:translateX="44"
+ android:translateY="28">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#3d90ff"
+ android:fillType="nonZero"
+ android:pathData=" M-0.65 -437.37 C-0.65,-437.37 8.33,-437.66 8.33,-437.66 C8.33,-437.66 17.31,-437.95 17.31,-437.95 C17.31,-437.95 26.25,-438.78 26.25,-438.78 C26.25,-438.78 35.16,-439.95 35.16,-439.95 C35.16,-439.95 44.07,-441.11 44.07,-441.11 C44.07,-441.11 52.85,-443 52.85,-443 C52.85,-443 61.6,-445.03 61.6,-445.03 C61.6,-445.03 70.35,-447.09 70.35,-447.09 C70.35,-447.09 78.91,-449.83 78.91,-449.83 C78.91,-449.83 87.43,-452.67 87.43,-452.67 C87.43,-452.67 95.79,-455.97 95.79,-455.97 C95.79,-455.97 104.11,-459.35 104.11,-459.35 C104.11,-459.35 112.36,-462.93 112.36,-462.93 C112.36,-462.93 120.6,-466.51 120.6,-466.51 C120.6,-466.51 128.84,-470.09 128.84,-470.09 C128.84,-470.09 137.09,-473.67 137.09,-473.67 C137.09,-473.67 145.49,-476.84 145.49,-476.84 C145.49,-476.84 153.9,-480.01 153.9,-480.01 C153.9,-480.01 162.31,-483.18 162.31,-483.18 C162.31,-483.18 170.98,-485.54 170.98,-485.54 C170.98,-485.54 179.66,-487.85 179.66,-487.85 C179.66,-487.85 188.35,-490.15 188.35,-490.15 C188.35,-490.15 197.22,-491.58 197.22,-491.58 C197.22,-491.58 206.09,-493.01 206.09,-493.01 C206.09,-493.01 214.98,-494.28 214.98,-494.28 C214.98,-494.28 223.95,-494.81 223.95,-494.81 C223.95,-494.81 232.93,-495.33 232.93,-495.33 C232.93,-495.33 241.9,-495.5 241.9,-495.5 C241.9,-495.5 250.88,-495.13 250.88,-495.13 C250.88,-495.13 259.86,-494.75 259.86,-494.75 C259.86,-494.75 268.78,-493.78 268.78,-493.78 C268.78,-493.78 277.68,-492.52 277.68,-492.52 C277.68,-492.52 286.57,-491.26 286.57,-491.26 C286.57,-491.26 295.31,-489.16 295.31,-489.16 C295.31,-489.16 304.04,-487.04 304.04,-487.04 C304.04,-487.04 312.7,-484.65 312.7,-484.65 C312.7,-484.65 321.19,-481.72 321.19,-481.72 C321.19,-481.72 329.68,-478.78 329.68,-478.78 C329.68,-478.78 337.96,-475.31 337.96,-475.31 C337.96,-475.31 346.14,-471.59 346.14,-471.59 C346.14,-471.59 354.3,-467.82 354.3,-467.82 C354.3,-467.82 362.11,-463.38 362.11,-463.38 C362.11,-463.38 369.92,-458.93 369.92,-458.93 C369.92,-458.93 377.53,-454.17 377.53,-454.17 C377.53,-454.17 384.91,-449.04 384.91,-449.04 C384.91,-449.04 392.29,-443.91 392.29,-443.91 C392.29,-443.91 399.26,-438.24 399.26,-438.24 C399.26,-438.24 406.15,-432.48 406.15,-432.48 C406.15,-432.48 412.92,-426.57 412.92,-426.57 C412.92,-426.57 419.27,-420.22 419.27,-420.22 C419.27,-420.22 425.62,-413.87 425.62,-413.87 C425.62,-413.87 431.61,-407.18 431.61,-407.18 C431.61,-407.18 437.38,-400.29 437.38,-400.29 C437.38,-400.29 443.14,-393.39 443.14,-393.39 C443.14,-393.39 448.27,-386.01 448.27,-386.01 C448.27,-386.01 453.4,-378.64 453.4,-378.64 C453.4,-378.64 458.26,-371.09 458.26,-371.09 C458.26,-371.09 462.71,-363.28 462.71,-363.28 C462.71,-363.28 467.16,-355.47 467.16,-355.47 C467.16,-355.47 471.03,-347.37 471.03,-347.37 C471.03,-347.37 474.75,-339.19 474.75,-339.19 C474.75,-339.19 478.34,-330.95 478.34,-330.95 C478.34,-330.95 481.28,-322.46 481.28,-322.46 C481.28,-322.46 484.21,-313.97 484.21,-313.97 C484.21,-313.97 486.72,-305.35 486.72,-305.35 C486.72,-305.35 488.84,-296.62 488.84,-296.62 C488.84,-296.62 490.96,-287.88 490.96,-287.88 C490.96,-287.88 492.33,-279.01 492.33,-279.01 C492.33,-279.01 493.59,-270.11 493.59,-270.11 C493.59,-270.11 494.69,-261.2 494.69,-261.2 C494.69,-261.2 495.07,-252.22 495.07,-252.22 C495.07,-252.22 495.44,-243.24 495.44,-243.24 C495.44,-243.24 495.41,-234.27 495.41,-234.27 C495.41,-234.27 494.88,-225.29 494.88,-225.29 C494.88,-225.29 494.35,-216.32 494.35,-216.32 C494.35,-216.32 493.22,-207.42 493.22,-207.42 C493.22,-207.42 491.79,-198.55 491.79,-198.55 C491.79,-198.55 490.36,-189.68 490.36,-189.68 C490.36,-189.68 488.19,-180.96 488.19,-180.96 C488.19,-180.96 485.88,-172.28 485.88,-172.28 C485.88,-172.28 483.56,-163.6 483.56,-163.6 C483.56,-163.6 480.48,-155.16 480.48,-155.16 C480.48,-155.16 477.31,-146.75 477.31,-146.75 C477.31,-146.75 474.14,-138.34 474.14,-138.34 C474.14,-138.34 470.62,-130.07 470.62,-130.07 C470.62,-130.07 467.04,-121.83 467.04,-121.83 C467.04,-121.83 463.46,-113.59 463.46,-113.59 C463.46,-113.59 459.88,-105.35 459.88,-105.35 C459.88,-105.35 456.54,-97.01 456.54,-97.01 C456.54,-97.01 453.37,-88.6 453.37,-88.6 C453.37,-88.6 450.21,-80.19 450.21,-80.19 C450.21,-80.19 447.68,-71.57 447.68,-71.57 C447.68,-71.57 445.36,-62.89 445.36,-62.89 C445.36,-62.89 443.04,-54.21 443.04,-54.21 C443.04,-54.21 441.54,-45.35 441.54,-45.35 C441.54,-45.35 440.09,-36.48 440.09,-36.48 C440.09,-36.48 438.78,-27.6 438.78,-27.6 C438.78,-27.6 438.19,-18.63 438.19,-18.63 C438.19,-18.63 437.61,-9.66 437.61,-9.66 C437.61,-9.66 437.36,-0.69 437.36,-0.69 C437.36,-0.69 437.65,8.29 437.65,8.29 C437.65,8.29 437.95,17.27 437.95,17.27 C437.95,17.27 438.77,26.21 438.77,26.21 C438.77,26.21 439.94,35.12 439.94,35.12 C439.94,35.12 441.11,44.03 441.11,44.03 C441.11,44.03 442.99,52.81 442.99,52.81 C442.99,52.81 445.02,61.57 445.02,61.57 C445.02,61.57 447.07,70.31 447.07,70.31 C447.07,70.31 449.82,78.87 449.82,78.87 C449.82,78.87 452.65,87.4 452.65,87.4 C452.65,87.4 455.96,95.75 455.96,95.75 C455.96,95.75 459.33,104.08 459.33,104.08 C459.33,104.08 462.91,112.32 462.91,112.32 C462.91,112.32 466.49,120.57 466.49,120.57 C466.49,120.57 470.07,128.81 470.07,128.81 C470.07,128.81 473.65,137.05 473.65,137.05 C473.65,137.05 476.82,145.46 476.82,145.46 C476.82,145.46 479.99,153.87 479.99,153.87 C479.99,153.87 483.17,162.28 483.17,162.28 C483.17,162.28 485.52,170.94 485.52,170.94 C485.52,170.94 487.84,179.63 487.84,179.63 C487.84,179.63 490.14,188.31 490.14,188.31 C490.14,188.31 491.57,197.18 491.57,197.18 C491.57,197.18 493,206.06 493,206.06 C493,206.06 494.27,214.95 494.27,214.95 C494.27,214.95 494.8,223.92 494.8,223.92 C494.8,223.92 495.33,232.89 495.33,232.89 C495.33,232.89 495.5,241.86 495.5,241.86 C495.5,241.86 495.12,250.84 495.12,250.84 C495.12,250.84 494.75,259.82 494.75,259.82 C494.75,259.82 493.78,268.74 493.78,268.74 C493.78,268.74 492.52,277.64 492.52,277.64 C492.52,277.64 491.27,286.54 491.27,286.54 C491.27,286.54 489.16,295.27 489.16,295.27 C489.16,295.27 487.05,304.01 487.05,304.01 C487.05,304.01 484.66,312.66 484.66,312.66 C484.66,312.66 481.73,321.16 481.73,321.16 C481.73,321.16 478.79,329.65 478.79,329.65 C478.79,329.65 475.32,337.93 475.32,337.93 C475.32,337.93 471.6,346.11 471.6,346.11 C471.6,346.11 467.84,354.27 467.84,354.27 C467.84,354.27 463.39,362.08 463.39,362.08 C463.39,362.08 458.94,369.89 458.94,369.89 C458.94,369.89 454.19,377.5 454.19,377.5 C454.19,377.5 449.06,384.88 449.06,384.88 C449.06,384.88 443.93,392.26 443.93,392.26 C443.93,392.26 438.26,399.23 438.26,399.23 C438.26,399.23 432.5,406.12 432.5,406.12 C432.5,406.12 426.6,412.89 426.6,412.89 C426.6,412.89 420.24,419.24 420.24,419.24 C420.24,419.24 413.89,425.6 413.89,425.6 C413.89,425.6 407.2,431.59 407.2,431.59 C407.2,431.59 400.31,437.36 400.31,437.36 C400.31,437.36 393.42,443.12 393.42,443.12 C393.42,443.12 386.04,448.25 386.04,448.25 C386.04,448.25 378.66,453.38 378.66,453.38 C378.66,453.38 371.11,458.24 371.11,458.24 C371.11,458.24 363.31,462.69 363.31,462.69 C363.31,462.69 355.5,467.14 355.5,467.14 C355.5,467.14 347.4,471.02 347.4,471.02 C347.4,471.02 339.22,474.73 339.22,474.73 C339.22,474.73 330.99,478.33 330.99,478.33 C330.99,478.33 322.49,481.27 322.49,481.27 C322.49,481.27 314,484.2 314,484.2 C314,484.2 305.38,486.71 305.38,486.71 C305.38,486.71 296.65,488.83 296.65,488.83 C296.65,488.83 287.91,490.95 287.91,490.95 C287.91,490.95 279.04,492.33 279.04,492.33 C279.04,492.33 270.14,493.59 270.14,493.59 C270.14,493.59 261.23,494.69 261.23,494.69 C261.23,494.69 252.25,495.07 252.25,495.07 C252.25,495.07 243.28,495.44 243.28,495.44 C243.28,495.44 234.3,495.41 234.3,495.41 C234.3,495.41 225.33,494.88 225.33,494.88 C225.33,494.88 216.36,494.35 216.36,494.35 C216.36,494.35 207.45,493.23 207.45,493.23 C207.45,493.23 198.58,491.8 198.58,491.8 C198.58,491.8 189.71,490.37 189.71,490.37 C189.71,490.37 180.99,488.21 180.99,488.21 C180.99,488.21 172.31,485.89 172.31,485.89 C172.31,485.89 163.63,483.57 163.63,483.57 C163.63,483.57 155.19,480.5 155.19,480.5 C155.19,480.5 146.78,477.32 146.78,477.32 C146.78,477.32 138.37,474.15 138.37,474.15 C138.37,474.15 130.11,470.63 130.11,470.63 C130.11,470.63 121.86,467.06 121.86,467.06 C121.86,467.06 113.62,463.48 113.62,463.48 C113.62,463.48 105.38,459.9 105.38,459.9 C105.38,459.9 97.04,456.56 97.04,456.56 C97.04,456.56 88.63,453.39 88.63,453.39 C88.63,453.39 80.22,450.22 80.22,450.22 C80.22,450.22 71.6,447.7 71.6,447.7 C71.6,447.7 62.92,445.37 62.92,445.37 C62.92,445.37 54.24,443.05 54.24,443.05 C54.24,443.05 45.38,441.55 45.38,441.55 C45.38,441.55 36.52,440.1 36.52,440.1 C36.52,440.1 27.63,438.78 27.63,438.78 C27.63,438.78 18.66,438.2 18.66,438.2 C18.66,438.2 9.7,437.61 9.7,437.61 C9.7,437.61 0.72,437.36 0.72,437.36 C0.72,437.36 -8.26,437.65 -8.26,437.65 C-8.26,437.65 -17.24,437.95 -17.24,437.95 C-17.24,437.95 -26.18,438.77 -26.18,438.77 C-26.18,438.77 -35.09,439.94 -35.09,439.94 C-35.09,439.94 -44,441.1 -44,441.1 C-44,441.1 -52.78,442.98 -52.78,442.98 C-52.78,442.98 -61.53,445.02 -61.53,445.02 C-61.53,445.02 -70.28,447.07 -70.28,447.07 C-70.28,447.07 -78.84,449.81 -78.84,449.81 C-78.84,449.81 -87.37,452.64 -87.37,452.64 C-87.37,452.64 -95.72,455.95 -95.72,455.95 C-95.72,455.95 -104.05,459.32 -104.05,459.32 C-104.05,459.32 -112.29,462.9 -112.29,462.9 C-112.29,462.9 -120.53,466.48 -120.53,466.48 C-120.53,466.48 -128.78,470.06 -128.78,470.06 C-128.78,470.06 -137.02,473.63 -137.02,473.63 C-137.02,473.63 -145.43,476.81 -145.43,476.81 C-145.43,476.81 -153.84,479.98 -153.84,479.98 C-153.84,479.98 -162.24,483.15 -162.24,483.15 C-162.24,483.15 -170.91,485.52 -170.91,485.52 C-170.91,485.52 -179.59,487.83 -179.59,487.83 C-179.59,487.83 -188.28,490.13 -188.28,490.13 C-188.28,490.13 -197.15,491.56 -197.15,491.56 C-197.15,491.56 -206.02,492.99 -206.02,492.99 C-206.02,492.99 -214.91,494.27 -214.91,494.27 C-214.91,494.27 -223.88,494.8 -223.88,494.8 C-223.88,494.8 -232.85,495.33 -232.85,495.33 C-232.85,495.33 -241.83,495.5 -241.83,495.5 C-241.83,495.5 -250.81,495.13 -250.81,495.13 C-250.81,495.13 -259.79,494.75 -259.79,494.75 C-259.79,494.75 -268.71,493.79 -268.71,493.79 C-268.71,493.79 -277.61,492.53 -277.61,492.53 C-277.61,492.53 -286.51,491.27 -286.51,491.27 C-286.51,491.27 -295.24,489.17 -295.24,489.17 C-295.24,489.17 -303.98,487.06 -303.98,487.06 C-303.98,487.06 -312.63,484.67 -312.63,484.67 C-312.63,484.67 -321.12,481.74 -321.12,481.74 C-321.12,481.74 -329.62,478.8 -329.62,478.8 C-329.62,478.8 -337.9,475.33 -337.9,475.33 C-337.9,475.33 -346.08,471.62 -346.08,471.62 C-346.08,471.62 -354.24,467.85 -354.24,467.85 C-354.24,467.85 -362.05,463.41 -362.05,463.41 C-362.05,463.41 -369.86,458.96 -369.86,458.96 C-369.86,458.96 -377.47,454.21 -377.47,454.21 C-377.47,454.21 -384.85,449.08 -384.85,449.08 C-384.85,449.08 -392.23,443.95 -392.23,443.95 C-392.23,443.95 -399.2,438.29 -399.2,438.29 C-399.2,438.29 -406.09,432.52 -406.09,432.52 C-406.09,432.52 -412.86,426.62 -412.86,426.62 C-412.86,426.62 -419.22,420.27 -419.22,420.27 C-419.22,420.27 -425.57,413.91 -425.57,413.91 C-425.57,413.91 -431.57,407.23 -431.57,407.23 C-431.57,407.23 -437.33,400.34 -437.33,400.34 C-437.33,400.34 -443.1,393.44 -443.1,393.44 C-443.1,393.44 -448.23,386.07 -448.23,386.07 C-448.23,386.07 -453.36,378.69 -453.36,378.69 C-453.36,378.69 -458.23,371.15 -458.23,371.15 C-458.23,371.15 -462.67,363.33 -462.67,363.33 C-462.67,363.33 -467.12,355.53 -467.12,355.53 C-467.12,355.53 -471,347.43 -471,347.43 C-471,347.43 -474.72,339.25 -474.72,339.25 C-474.72,339.25 -478.32,331.02 -478.32,331.02 C-478.32,331.02 -481.25,322.52 -481.25,322.52 C-481.25,322.52 -484.19,314.03 -484.19,314.03 C-484.19,314.03 -486.71,305.42 -486.71,305.42 C-486.71,305.42 -488.82,296.68 -488.82,296.68 C-488.82,296.68 -490.94,287.95 -490.94,287.95 C-490.94,287.95 -492.32,279.07 -492.32,279.07 C-492.32,279.07 -493.58,270.18 -493.58,270.18 C-493.58,270.18 -494.69,261.27 -494.69,261.27 C-494.69,261.27 -495.07,252.29 -495.07,252.29 C-495.07,252.29 -495.44,243.31 -495.44,243.31 C-495.44,243.31 -495.42,234.33 -495.42,234.33 C-495.42,234.33 -494.89,225.36 -494.89,225.36 C-494.89,225.36 -494.36,216.39 -494.36,216.39 C-494.36,216.39 -493.23,207.49 -493.23,207.49 C-493.23,207.49 -491.8,198.61 -491.8,198.61 C-491.8,198.61 -490.37,189.74 -490.37,189.74 C-490.37,189.74 -488.22,181.02 -488.22,181.02 C-488.22,181.02 -485.9,172.34 -485.9,172.34 C-485.9,172.34 -483.58,163.66 -483.58,163.66 C-483.58,163.66 -480.51,155.22 -480.51,155.22 C-480.51,155.22 -477.34,146.81 -477.34,146.81 C-477.34,146.81 -474.17,138.41 -474.17,138.41 C-474.17,138.41 -470.65,130.14 -470.65,130.14 C-470.65,130.14 -467.07,121.9 -467.07,121.9 C-467.07,121.9 -463.49,113.65 -463.49,113.65 C-463.49,113.65 -459.91,105.41 -459.91,105.41 C-459.91,105.41 -456.57,97.07 -456.57,97.07 C-456.57,97.07 -453.4,88.66 -453.4,88.66 C-453.4,88.66 -450.23,80.25 -450.23,80.25 C-450.23,80.25 -447.7,71.64 -447.7,71.64 C-447.7,71.64 -445.38,62.96 -445.38,62.96 C-445.38,62.96 -443.06,54.28 -443.06,54.28 C-443.06,54.28 -441.56,45.42 -441.56,45.42 C-441.56,45.42 -440.1,36.55 -440.1,36.55 C-440.1,36.55 -438.78,27.67 -438.78,27.67 C-438.78,27.67 -438.2,18.7 -438.2,18.7 C-438.2,18.7 -437.62,9.73 -437.62,9.73 C-437.62,9.73 -437.36,0.76 -437.36,0.76 C-437.36,0.76 -437.66,-8.22 -437.66,-8.22 C-437.66,-8.22 -437.95,-17.2 -437.95,-17.2 C-437.95,-17.2 -438.77,-26.14 -438.77,-26.14 C-438.77,-26.14 -439.93,-35.05 -439.93,-35.05 C-439.93,-35.05 -441.1,-43.96 -441.1,-43.96 C-441.1,-43.96 -442.98,-52.75 -442.98,-52.75 C-442.98,-52.75 -445.01,-61.5 -445.01,-61.5 C-445.01,-61.5 -447.06,-70.25 -447.06,-70.25 C-447.06,-70.25 -449.8,-78.81 -449.8,-78.81 C-449.8,-78.81 -452.63,-87.33 -452.63,-87.33 C-452.63,-87.33 -455.94,-95.69 -455.94,-95.69 C-455.94,-95.69 -459.31,-104.02 -459.31,-104.02 C-459.31,-104.02 -462.89,-112.26 -462.89,-112.26 C-462.89,-112.26 -466.47,-120.5 -466.47,-120.5 C-466.47,-120.5 -470.05,-128.74 -470.05,-128.74 C-470.05,-128.74 -473.68,-137.12 -473.68,-137.12 C-473.68,-137.12 -476.85,-145.53 -476.85,-145.53 C-476.85,-145.53 -480.03,-153.94 -480.03,-153.94 C-480.03,-153.94 -483.2,-162.34 -483.2,-162.34 C-483.2,-162.34 -485.55,-171.02 -485.55,-171.02 C-485.55,-171.02 -487.86,-179.7 -487.86,-179.7 C-487.86,-179.7 -490.15,-188.39 -490.15,-188.39 C-490.15,-188.39 -491.58,-197.26 -491.58,-197.26 C-491.58,-197.26 -493.01,-206.13 -493.01,-206.13 C-493.01,-206.13 -494.28,-215.02 -494.28,-215.02 C-494.28,-215.02 -494.81,-223.99 -494.81,-223.99 C-494.81,-223.99 -495.33,-232.96 -495.33,-232.96 C-495.33,-232.96 -495.5,-241.94 -495.5,-241.94 C-495.5,-241.94 -495.12,-250.92 -495.12,-250.92 C-495.12,-250.92 -494.75,-259.9 -494.75,-259.9 C-494.75,-259.9 -493.78,-268.82 -493.78,-268.82 C-493.78,-268.82 -492.52,-277.72 -492.52,-277.72 C-492.52,-277.72 -491.26,-286.61 -491.26,-286.61 C-491.26,-286.61 -489.15,-295.35 -489.15,-295.35 C-489.15,-295.35 -487.03,-304.08 -487.03,-304.08 C-487.03,-304.08 -484.64,-312.73 -484.64,-312.73 C-484.64,-312.73 -481.7,-321.23 -481.7,-321.23 C-481.7,-321.23 -478.77,-329.72 -478.77,-329.72 C-478.77,-329.72 -475.29,-338 -475.29,-338 C-475.29,-338 -471.57,-346.18 -471.57,-346.18 C-471.57,-346.18 -467.8,-354.33 -467.8,-354.33 C-467.8,-354.33 -463.36,-362.14 -463.36,-362.14 C-463.36,-362.14 -458.91,-369.95 -458.91,-369.95 C-458.91,-369.95 -454.15,-377.56 -454.15,-377.56 C-454.15,-377.56 -449.02,-384.94 -449.02,-384.94 C-449.02,-384.94 -443.88,-392.32 -443.88,-392.32 C-443.88,-392.32 -438.22,-399.28 -438.22,-399.28 C-438.22,-399.28 -432.45,-406.18 -432.45,-406.18 C-432.45,-406.18 -426.55,-412.94 -426.55,-412.94 C-426.55,-412.94 -420.19,-419.3 -420.19,-419.3 C-420.19,-419.3 -413.84,-425.65 -413.84,-425.65 C-413.84,-425.65 -407.15,-431.64 -407.15,-431.64 C-407.15,-431.64 -400.26,-437.41 -400.26,-437.41 C-400.26,-437.41 -393.36,-443.16 -393.36,-443.16 C-393.36,-443.16 -385.98,-448.29 -385.98,-448.29 C-385.98,-448.29 -378.6,-453.43 -378.6,-453.43 C-378.6,-453.43 -371.05,-458.28 -371.05,-458.28 C-371.05,-458.28 -363.24,-462.73 -363.24,-462.73 C-363.24,-462.73 -355.43,-467.18 -355.43,-467.18 C-355.43,-467.18 -347.33,-471.05 -347.33,-471.05 C-347.33,-471.05 -339.15,-474.76 -339.15,-474.76 C-339.15,-474.76 -330.92,-478.35 -330.92,-478.35 C-330.92,-478.35 -322.42,-481.29 -322.42,-481.29 C-322.42,-481.29 -313.93,-484.23 -313.93,-484.23 C-313.93,-484.23 -305.31,-486.73 -305.31,-486.73 C-305.31,-486.73 -296.58,-488.85 -296.58,-488.85 C-296.58,-488.85 -287.85,-490.97 -287.85,-490.97 C-287.85,-490.97 -278.97,-492.34 -278.97,-492.34 C-278.97,-492.34 -270.07,-493.6 -270.07,-493.6 C-270.07,-493.6 -261.16,-494.7 -261.16,-494.7 C-261.16,-494.7 -252.18,-495.07 -252.18,-495.07 C-252.18,-495.07 -243.2,-495.44 -243.2,-495.44 C-243.2,-495.44 -234.23,-495.41 -234.23,-495.41 C-234.23,-495.41 -225.26,-494.88 -225.26,-494.88 C-225.26,-494.88 -216.29,-494.35 -216.29,-494.35 C-216.29,-494.35 -207.38,-493.22 -207.38,-493.22 C-207.38,-493.22 -198.51,-491.79 -198.51,-491.79 C-198.51,-491.79 -189.64,-490.36 -189.64,-490.36 C-189.64,-490.36 -180.92,-488.19 -180.92,-488.19 C-180.92,-488.19 -172.24,-485.87 -172.24,-485.87 C-172.24,-485.87 -163.56,-483.56 -163.56,-483.56 C-163.56,-483.56 -155.12,-480.47 -155.12,-480.47 C-155.12,-480.47 -146.72,-477.3 -146.72,-477.3 C-146.72,-477.3 -138.31,-474.13 -138.31,-474.13 C-138.31,-474.13 -130.04,-470.61 -130.04,-470.61 C-130.04,-470.61 -121.8,-467.03 -121.8,-467.03 C-121.8,-467.03 -113.55,-463.45 -113.55,-463.45 C-113.55,-463.45 -105.31,-459.87 -105.31,-459.87 C-105.31,-459.87 -96.97,-456.53 -96.97,-456.53 C-96.97,-456.53 -88.56,-453.37 -88.56,-453.37 C-88.56,-453.37 -80.15,-450.2 -80.15,-450.2 C-80.15,-450.2 -71.53,-447.68 -71.53,-447.68 C-71.53,-447.68 -62.85,-445.36 -62.85,-445.36 C-62.85,-445.36 -54.17,-443.04 -54.17,-443.04 C-54.17,-443.04 -45.31,-441.54 -45.31,-441.54 C-45.31,-441.54 -36.44,-440.09 -36.44,-440.09 C-36.44,-440.09 -27.56,-438.78 -27.56,-438.78 C-27.56,-438.78 -18.59,-438.19 -18.59,-438.19 C-18.59,-438.19 -9.62,-437.61 -9.62,-437.61 C-9.62,-437.61 -0.65,-437.37 -0.65,-437.37c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_pause_button.xml b/packages/SystemUI/res/drawable/ic_media_pause_button.xml
new file mode 100644
index 000000000000..6ae89f91c5ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_pause_button.xml
@@ -0,0 +1,135 @@
+<?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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-5.06 -18 C-5.06,-18 -5.06,-1.24 -5.06,-1.24 C-5.06,-1.24 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c "
+ android:valueTo="M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.449,0 0,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="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-5.06 -18 C-5.06,-18 -5.06,-0.75 -5.06,-0.75 C-5.06,-0.75 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c "
+ android:valueTo="M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.449,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="56"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="15.485"
+ android:valueTo="12.321"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="278"
+ android:propertyName="translateX"
+ android:startOffset="56"
+ android:valueFrom="12.321"
+ android:valueTo="7.576"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,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="517"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="-12.031"
+ android:scaleX="0.33299999999999996"
+ android:scaleY="0.33299999999999996"
+ android:translateX="19.524"
+ android:translateY="12.084">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-5.06 -18 C-5.06,-18 -5.06,-1.24 -5.06,-1.24 C-5.06,-1.24 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:scaleX="0.33299999999999996"
+ android:scaleY="0.33299999999999996"
+ android:translateX="15.485"
+ android:translateY="12.084">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12.031">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-5.06 -18 C-5.06,-18 -5.06,-0.75 -5.06,-0.75 C-5.06,-0.75 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c " />
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_pause_button_container.xml b/packages/SystemUI/res/drawable/ic_media_pause_button_container.xml
new file mode 100644
index 000000000000..571f69d51ac4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_pause_button_container.xml
@@ -0,0 +1,135 @@
+<?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.
+ -->
+<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="88dp"
+ android:height="56dp"
+ android:viewportHeight="56"
+ android:viewportWidth="88">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:pivotX="0.493"
+ android:pivotY="0.124"
+ android:scaleX="1.05905"
+ android:scaleY="1.0972"
+ android:translateX="43.528999999999996"
+ android:translateY="27.898">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#3d90ff"
+ android:fillType="nonZero"
+ android:pathData=" M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="133"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c "
+ android:valueTo="M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.473,0 0.065,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="367"
+ android:propertyName="pathData"
+ android:startOffset="133"
+ android:valueFrom="M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c "
+ android:valueTo="M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.473,0 0.065,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="scaleX"
+ android:startOffset="0"
+ android:valueFrom="1.05905"
+ android:valueTo="1.17758"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.226,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="scaleY"
+ android:startOffset="0"
+ android:valueFrom="1.0972"
+ android:valueTo="1.22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.226,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="scaleX"
+ android:startOffset="167"
+ android:valueFrom="1.17758"
+ android:valueTo="1.05905"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.213,0 0.248,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="scaleY"
+ android:startOffset="167"
+ android:valueFrom="1.22"
+ android:valueTo="1.0972"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.213,0 0.248,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="517"
+ 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/SystemUI/res/drawable/ic_media_play_button.xml b/packages/SystemUI/res/drawable/ic_media_play_button.xml
new file mode 100644
index 000000000000..f64690268cfe
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_play_button.xml
@@ -0,0 +1,124 @@
+<?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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c "
+ android:valueTo="M-5.06 -18 C-5.06,-18 -5.06,-1.24 -5.06,-1.24 C-5.06,-1.24 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.433,0 0,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="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c "
+ android:valueTo="M-5.06 -18 C-5.06,-18 -5.06,-0.75 -5.06,-0.75 C-5.06,-0.75 -5.06,17.7 -5.06,17.7 C-5.06,19.36 -6.41,20.7 -8.06,20.7 C-8.06,20.7 -16,20.7 -16,20.7 C-17.66,20.7 -19,19.36 -19,17.7 C-19,17.7 -19,-18 -19,-18 C-19,-19.66 -17.66,-21 -16,-21 C-16,-21 -8.06,-21 -8.06,-21 C-6.41,-21 -5.06,-19.66 -5.06,-18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.433,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="7.576"
+ android:valueTo="15.485"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.583,0 0.089,0.874 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="517"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="-12.031"
+ android:scaleX="0.33299999999999996"
+ android:scaleY="0.33299999999999996"
+ android:translateX="19.524"
+ android:translateY="12.084">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:scaleX="0.33299999999999996"
+ android:scaleY="0.33299999999999996"
+ android:translateX="7.576"
+ android:translateY="12.084">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12.031">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-4.69 -16.69 C-4.69,-16.69 20.25,-1.25 20.31,0.25 C20.38,1.75 -4.88,16.89 -4.88,16.89 C-6.94,18.25 -8.56,19.4 -9.75,19.58 C-10.94,19.75 -12.19,18.94 -12.12,16.14 C-12.09,14.76 -12.12,15.92 -12.12,14.26 C-12.12,14.26 -11.94,-16.44 -11.94,-16.44 C-11.94,-18.09 -12.09,-19.69 -10.44,-19.69 C-10.44,-19.69 -9.5,-19.56 -9.5,-19.56 C-8.62,-19.12 -6.19,-17.44 -4.69,-16.69c " />
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_media_play_button_container.xml b/packages/SystemUI/res/drawable/ic_media_play_button_container.xml
new file mode 100644
index 000000000000..aa4e09fa4033
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_play_button_container.xml
@@ -0,0 +1,135 @@
+<?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.
+ -->
+<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="56dp"
+ android:width="88dp"
+ android:viewportHeight="56"
+ android:viewportWidth="88">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="43.528999999999996"
+ android:translateY="27.898"
+ android:pivotX="0.493"
+ android:pivotY="0.124"
+ android:scaleX="1.05905"
+ android:scaleY="1.0972">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#3d90ff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="167"
+ android:startOffset="0"
+ android:valueFrom="M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "
+ android:valueTo="M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.493,0 0,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="167"
+ android:valueFrom="M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c "
+ android:valueTo="M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.493,0 0,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="167"
+ android:startOffset="0"
+ android:valueFrom="1.05905"
+ android:valueTo="1.17758"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.226,0 0.667,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="167"
+ android:startOffset="0"
+ android:valueFrom="1.0972"
+ android:valueTo="1.22"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.226,0 0.667,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="333"
+ android:startOffset="167"
+ android:valueFrom="1.17758"
+ android:valueTo="1.05905"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.213,0 0.248,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="333"
+ android:startOffset="167"
+ android:valueFrom="1.22"
+ android:valueTo="1.0972"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.213,0 0.248,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:propertyName="translateX"
+ android:duration="517"
+ 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/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 0b624e1687a6..58f2d3ccc6a8 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -44,7 +44,7 @@
app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container"
app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"/>
+ app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/volume_dialog_main_slider_container"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 967cb3fd68de..6eb7b730e105 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -14,8 +14,9 @@
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/volume_dialog_slider_width"
- android:layout_height="@dimen/volume_dialog_slider_height">
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:maxHeight="@dimen/volume_dialog_slider_height">
<com.google.android.material.slider.Slider
android:id="@+id/volume_dialog_slider"
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
index e65d0b938b65..6748cfa05c35 100644
--- a/packages/SystemUI/res/layout/volume_ringer_button.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -20,10 +20,9 @@
<ImageButton
android:id="@+id/volume_drawer_button"
- android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
- android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
- android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
android:contentDescription="@string/volume_ringer_mode"
android:gravity="center"
android:tint="@androidprv:color/materialColorOnSurface"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8bf4e373a6e0..2ffa3d19e161 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1278,6 +1278,7 @@
<dimen name="qs_center_guideline_padding">10dp</dimen>
<dimen name="qs_media_action_spacing">4dp</dimen>
<dimen name="qs_media_action_margin">12dp</dimen>
+ <dimen name="qs_media_action_play_pause_width">72dp</dimen>
<dimen name="qs_seamless_height">24dp</dimen>
<dimen name="qs_seamless_icon_size">12dp</dimen>
<dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
@@ -2116,6 +2117,11 @@
<dimen name="volume_dialog_button_size">40dp</dimen>
<dimen name="volume_dialog_slider_width">52dp</dimen>
<dimen name="volume_dialog_slider_height">254dp</dimen>
+ <!--
+ A primary goal of this margin is to vertically constraint slider height in the landscape
+ orientation when the vertical space is limited
+ -->
+ <dimen name="volume_dialog_slider_vertical_margin">124dp</dimen>
<fraction name="volume_dialog_half_opened_bias">0.2</fraction>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a01ff3d5258f..d445ed927a25 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3919,6 +3919,16 @@
The helper is a component that shows the user which keyboard shortcuts they can use.
[CHAR LIMIT=NONE] -->
<string name="shortcut_helper_plus_symbol">+</string>
+ <!-- Accessibility label for the plus icon on a shortcut in shortcut helper that allows the user
+ to add a new custom shortcut.
+ The helper is a component that shows the user which keyboard shortcuts they can use.
+ [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_add_shortcut_button_label">Add shortcut</string>
+ <!-- Accessibility label for the bin(trash) icon on a shortcut in shortcut helper that allows the
+ user to delete an existing custom shortcut.
+ The helper is a component that shows the user which keyboard shortcuts they can use.
+ [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_delete_shortcut_button_label">Delete shortcut</string>
<!-- Keyboard touchpad tutorial scheduler-->
<!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
diff --git a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
index 9018e5b7ed92..a8f616c2427d 100644
--- a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
@@ -6,10 +6,13 @@
<Constraint
android:id="@id/volume_dialog_main_slider_container"
android:layout_width="@dimen/volume_dialog_slider_width"
- android:layout_height="@dimen/volume_dialog_slider_height"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/volume_dialog_slider_vertical_margin"
android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ android:layout_marginBottom="@dimen/volume_dialog_slider_vertical_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHeight_max="@dimen/volume_dialog_slider_height"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
index 297c38873164..b4d8ae791f36 100644
--- a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
@@ -6,10 +6,13 @@
<Constraint
android:id="@id/volume_dialog_main_slider_container"
android:layout_width="@dimen/volume_dialog_slider_width"
- android:layout_height="@dimen/volume_dialog_slider_height"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/volume_dialog_slider_vertical_margin"
android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ android:layout_marginBottom="@dimen/volume_dialog_slider_vertical_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHeight_max="@dimen/volume_dialog_slider_height"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="@fraction/volume_dialog_half_opened_bias" />
</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index 274fa59045d7..a16b4a6892b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -53,11 +53,11 @@ constructor(
override suspend fun onActivated(): Nothing {
viewModel.shortcutCustomizationUiState.collect { uiState ->
- when(uiState){
+ when (uiState) {
is AddShortcutDialog,
is DeleteShortcutDialog,
is ResetShortcutDialog -> {
- if (dialog == null){
+ if (dialog == null) {
dialog = createDialog().also { it.show() }
}
}
@@ -85,7 +85,9 @@ constructor(
ShortcutCustomizationDialog(
uiState = uiState,
modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp),
- onKeyPress = { viewModel.onKeyPressed(it) },
+ onShortcutKeyCombinationSelected = {
+ viewModel.onShortcutKeyCombinationSelected(it)
+ },
onCancel = { dialog.dismiss() },
onConfirmSetShortcut = { coroutineScope.launch { viewModel.onSetShortcut() } },
onConfirmDeleteShortcut = {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index 3819f6d41856..d9e55f89cda5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -49,8 +49,12 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.key.type
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@@ -65,7 +69,7 @@ import com.android.systemui.res.R
fun ShortcutCustomizationDialog(
uiState: ShortcutCustomizationUiState,
modifier: Modifier = Modifier,
- onKeyPress: (KeyEvent) -> Boolean,
+ onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
onCancel: () -> Unit,
onConfirmSetShortcut: () -> Unit,
onConfirmDeleteShortcut: () -> Unit,
@@ -73,7 +77,13 @@ fun ShortcutCustomizationDialog(
) {
when (uiState) {
is ShortcutCustomizationUiState.AddShortcutDialog -> {
- AddShortcutDialog(modifier, uiState, onKeyPress, onCancel, onConfirmSetShortcut)
+ AddShortcutDialog(
+ modifier,
+ uiState,
+ onShortcutKeyCombinationSelected,
+ onCancel,
+ onConfirmSetShortcut,
+ )
}
is ShortcutCustomizationUiState.DeleteShortcutDialog -> {
DeleteShortcutDialog(modifier, onCancel, onConfirmDeleteShortcut)
@@ -91,29 +101,27 @@ fun ShortcutCustomizationDialog(
private fun AddShortcutDialog(
modifier: Modifier,
uiState: ShortcutCustomizationUiState.AddShortcutDialog,
- onKeyPress: (KeyEvent) -> Boolean,
+ onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
onCancel: () -> Unit,
- onConfirmSetShortcut: () -> Unit
-){
+ onConfirmSetShortcut: () -> Unit,
+) {
Column(modifier = modifier) {
Title(uiState.shortcutLabel)
Description(
- text =
- stringResource(
- id = R.string.shortcut_customize_mode_add_shortcut_description
- )
+ text = stringResource(id = R.string.shortcut_customize_mode_add_shortcut_description)
)
PromptShortcutModifier(
modifier =
- Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
- .width(131.dp)
- .height(48.dp),
+ Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
+ .width(131.dp)
+ .height(48.dp),
defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
)
SelectedKeyCombinationContainer(
shouldShowError = uiState.errorMessage.isNotEmpty(),
- onKeyPress = onKeyPress,
+ onShortcutKeyCombinationSelected = onShortcutKeyCombinationSelected,
pressedKeys = uiState.pressedKeys,
+ onConfirmSetShortcut = onConfirmSetShortcut,
)
ErrorMessageContainer(uiState.errorMessage)
DialogButtons(
@@ -121,9 +129,7 @@ private fun AddShortcutDialog(
isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
onConfirm = onConfirmSetShortcut,
confirmButtonText =
- stringResource(
- R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
- ),
+ stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label),
)
}
}
@@ -132,20 +138,15 @@ private fun AddShortcutDialog(
private fun DeleteShortcutDialog(
modifier: Modifier,
onCancel: () -> Unit,
- onConfirmDeleteShortcut: () -> Unit
-){
+ onConfirmDeleteShortcut: () -> Unit,
+) {
ConfirmationDialog(
modifier = modifier,
- title =
- stringResource(
- id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
- ),
+ title = stringResource(id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title),
description =
- stringResource(
- id = R.string.shortcut_customize_mode_remove_shortcut_description
- ),
+ stringResource(id = R.string.shortcut_customize_mode_remove_shortcut_description),
confirmButtonText =
- stringResource(R.string.shortcut_helper_customize_dialog_remove_button_label),
+ stringResource(R.string.shortcut_helper_customize_dialog_remove_button_label),
onCancel = onCancel,
onConfirm = onConfirmDeleteShortcut,
)
@@ -155,20 +156,15 @@ private fun DeleteShortcutDialog(
private fun ResetShortcutDialog(
modifier: Modifier,
onCancel: () -> Unit,
- onConfirmResetShortcut: () -> Unit
-){
+ onConfirmResetShortcut: () -> Unit,
+) {
ConfirmationDialog(
modifier = modifier,
- title =
- stringResource(
- id = R.string.shortcut_customize_mode_reset_shortcut_dialog_title
- ),
+ title = stringResource(id = R.string.shortcut_customize_mode_reset_shortcut_dialog_title),
description =
- stringResource(
- id = R.string.shortcut_customize_mode_reset_shortcut_description
- ),
+ stringResource(id = R.string.shortcut_customize_mode_reset_shortcut_description),
confirmButtonText =
- stringResource(R.string.shortcut_helper_customize_dialog_reset_button_label),
+ stringResource(R.string.shortcut_helper_customize_dialog_reset_button_label),
onCancel = onCancel,
onConfirm = onConfirmResetShortcut,
)
@@ -201,6 +197,9 @@ private fun DialogButtons(
onConfirm: () -> Unit,
confirmButtonText: String,
) {
+ val focusRequester = remember { FocusRequester() }
+ LaunchedEffect(Unit) { focusRequester.requestFocus() }
+
Row(
modifier =
Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp)
@@ -218,6 +217,10 @@ private fun DialogButtons(
)
Spacer(modifier = Modifier.width(8.dp))
ShortcutHelperButton(
+ modifier =
+ Modifier.focusRequester(focusRequester).focusProperties {
+ canFocus = true
+ }, // enable focus on touch/click mode
onClick = onConfirm,
color = MaterialTheme.colorScheme.primary,
width = 116.dp,
@@ -248,8 +251,9 @@ private fun ErrorMessageContainer(errorMessage: String) {
@Composable
private fun SelectedKeyCombinationContainer(
shouldShowError: Boolean,
- onKeyPress: (KeyEvent) -> Boolean,
+ onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
pressedKeys: List<ShortcutKey>,
+ onConfirmSetShortcut: () -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
@@ -269,7 +273,17 @@ private fun SelectedKeyCombinationContainer(
Modifier.padding(all = 16.dp)
.sizeIn(minWidth = 332.dp, minHeight = 56.dp)
.border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp))
- .onKeyEvent { onKeyPress(it) }
+ .onPreviewKeyEvent { keyEvent ->
+ val keyEventProcessed = onShortcutKeyCombinationSelected(keyEvent)
+ if (
+ !keyEventProcessed &&
+ keyEvent.key == Key.Enter &&
+ keyEvent.type == KeyEventType.KeyUp
+ ) {
+ onConfirmSetShortcut()
+ true
+ } else keyEventProcessed
+ }
.focusProperties { canFocus = true } // enables keyboard focus when in touch mode
.focusRequester(focusRequester),
interactionSource = interactionSource,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index aea583d67289..ba31d08c9c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -729,6 +729,7 @@ private fun AddShortcutButton(onClick: () -> Unit) {
contentColor = MaterialTheme.colorScheme.primary,
contentPaddingVertical = 0.dp,
contentPaddingHorizontal = 0.dp,
+ contentDescription = stringResource(R.string.shortcut_helper_add_shortcut_button_label),
)
}
@@ -749,6 +750,7 @@ private fun DeleteShortcutButton(onClick: () -> Unit) {
contentColor = MaterialTheme.colorScheme.primary,
contentPaddingVertical = 0.dp,
contentPaddingHorizontal = 0.dp,
+ contentDescription = stringResource(R.string.shortcut_helper_delete_shortcut_button_label),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 55c0fe297bcb..9a380f495176 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -230,6 +230,7 @@ fun ShortcutHelperButton(
contentPaddingVertical: Dp = 10.dp,
enabled: Boolean = true,
border: BorderStroke? = null,
+ contentDescription: String? = null,
) {
ShortcutHelperButtonSurface(
onClick = onClick,
@@ -254,8 +255,7 @@ fun ShortcutHelperButton(
Icon(
tint = contentColor,
imageVector = iconSource.imageVector,
- contentDescription =
- null, // TODO this probably should not be null for accessibility.
+ contentDescription = contentDescription,
modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index 373eb250d61d..915a66c43a12 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -46,6 +46,7 @@ constructor(
private val context: Context,
private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor,
) {
+ private var keyDownEventCache: KeyEvent? = null
private val _shortcutCustomizationUiState =
MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive)
@@ -94,9 +95,16 @@ constructor(
shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null)
}
- fun onKeyPressed(keyEvent: KeyEvent): Boolean {
- if ((keyEvent.isMetaPressed && keyEvent.type == KeyEventType.KeyDown)) {
- updatePressedKeys(keyEvent)
+ fun onShortcutKeyCombinationSelected(keyEvent: KeyEvent): Boolean {
+ if (isModifier(keyEvent)) {
+ return false
+ }
+ if (keyEvent.isMetaPressed && keyEvent.type == KeyEventType.KeyDown) {
+ keyDownEventCache = keyEvent
+ return true
+ } else if (keyEvent.type == KeyEventType.KeyUp && keyEvent.key == keyDownEventCache?.key) {
+ updatePressedKeys(keyDownEventCache!!)
+ clearKeyDownEventCache()
return true
}
return false
@@ -157,16 +165,21 @@ constructor(
return (uiState as? AddShortcutDialog)?.copy(errorMessage = errorMessage) ?: uiState
}
+ private fun isModifier(keyEvent: KeyEvent) = SUPPORTED_MODIFIERS.contains(keyEvent.key)
+
private fun updatePressedKeys(keyEvent: KeyEvent) {
- val isModifier = SUPPORTED_MODIFIERS.contains(keyEvent.key)
val keyCombination =
KeyCombination(
modifiers = keyEvent.nativeKeyEvent.modifiers,
- keyCode = if (!isModifier) keyEvent.key.nativeKeyCode else null,
+ keyCode = if (!isModifier(keyEvent)) keyEvent.key.nativeKeyCode else null,
)
shortcutCustomizationInteractor.updateUserSelectedKeyCombination(keyCombination)
}
+ private fun clearKeyDownEventCache() {
+ keyDownEventCache = null
+ }
+
@AssistedFactory
interface Factory {
fun create(): ShortcutCustomizationViewModel
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 9896365abff9..b42da5265d86 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
@@ -132,6 +132,8 @@ constructor(
if (SceneContainerFlag.isEnabled) return@collect
startTransitionTo(
toState = KeyguardState.GONE,
+ modeOnCanceled = TransitionModeOnCanceled.REVERSE,
+ ownerReason = "canWakeDirectlyToGone = true",
)
} else if (shouldTransitionToLockscreen) {
val modeOnCanceled =
@@ -146,7 +148,7 @@ constructor(
startTransitionTo(
toState = KeyguardState.LOCKSCREEN,
modeOnCanceled = modeOnCanceled,
- ownerReason = "listen for aod to awake"
+ ownerReason = "listen for aod to awake",
)
} else if (shouldTransitionToOccluded) {
startTransitionTo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index fbe31bbf36e6..8f7f2a0a8cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -44,7 +44,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -84,7 +83,6 @@ class KeyguardInteractor
@Inject
constructor(
private val repository: KeyguardRepository,
- powerInteractor: PowerInteractor,
bouncerRepository: KeyguardBouncerRepository,
@ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
shadeRepository: ShadeRepository,
@@ -216,11 +214,7 @@ constructor(
// should actually be quite strange to leave AOD and then go straight to
// DREAMING so this should be fine.
delay(IS_ABLE_TO_DREAM_DELAY_MS)
- isDreaming
- .sample(powerInteractor.isAwake) { isDreaming, isAwake ->
- isDreaming && isAwake
- }
- .debounce(50L)
+ isDreaming.debounce(50L)
} else {
flowOf(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index a133f06b3f41..3bdc32dce6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -116,9 +116,10 @@ constructor(
* - We're wake and unlocking (fingerprint auth occurred while asleep).
* - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
* - We're DREAMING and dismissible.
- * - We're already GONE. Technically you're already awake when GONE, but this makes it easier to
- * reason about this state (for example, if canWakeDirectlyToGone, don't tell WM to pause the
- * top activity - something you should never do while GONE as well).
+ * - We're already GONE and not transitioning out of GONE. Technically you're already awake when
+ * GONE, but this makes it easier to reason about this state (for example, if
+ * canWakeDirectlyToGone, don't tell WM to pause the top activity - something you should never
+ * do while GONE as well).
*/
val canWakeDirectlyToGone =
combine(
@@ -138,7 +139,8 @@ constructor(
canIgnoreAuthAndReturnToGone ||
(currentState == KeyguardState.DREAMING &&
keyguardInteractor.isKeyguardDismissible.value) ||
- currentState == KeyguardState.GONE
+ (currentState == KeyguardState.GONE &&
+ transitionInteractor.getStartedState() == KeyguardState.GONE)
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
index 542fb9b46bef..3eb8522e0338 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
@@ -23,4 +23,10 @@ data class BlurConfig(val minBlurRadiusPx: Float, val maxBlurRadiusPx: Float) {
// No-op config that will be used by dagger of other SysUI variants which don't blur the
// background surface.
@Inject constructor() : this(0.0f, 0.0f)
+
+ companion object {
+ // Blur the shade much lesser than the background surface so that the surface is
+ // distinguishable from the background.
+ @JvmStatic fun Float.maxBlurRadiusToNotificationPanelBlurRadius(): Float = this / 3.0f
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt
index e77e9dd9e9ed..eb1afb406d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt
@@ -30,6 +30,9 @@ interface PrimaryBouncerTransition {
/** Radius of blur applied to the window's root view. */
val windowBlurRadius: Flow<Float>
+ /** Radius of blur applied to the notifications on expanded shade */
+ val notificationBlurRadius: Flow<Float>
+
fun transitionProgressToBlurRadius(
starBlurRadius: Float,
endBlurRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index f17455788d6e..92bb5e6029cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -23,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.BlurConfig
+import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -73,7 +75,28 @@ constructor(
val lockscreenAlpha: Flow<Float> = if (WindowBlurFlag.isEnabled) alphaFlow else emptyFlow()
- val notificationAlpha: Flow<Float> = alphaFlow
+ val notificationAlpha: Flow<Float> =
+ if (Flags.bouncerUiRevamp()) {
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded = lockscreenAlpha,
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(1f),
+ )
+ } else {
+ alphaFlow
+ }
+
+ override val notificationBlurRadius: Flow<Float> =
+ if (Flags.bouncerUiRevamp()) {
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded = emptyFlow(),
+ flowWhenShadeIsExpanded =
+ transitionAnimation.immediatelyTransitionTo(
+ blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius()
+ ),
+ )
+ } else {
+ emptyFlow<Float>()
+ }
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
index dbb6a49e7844..e3b55874de6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
@@ -53,4 +53,7 @@ constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFl
override val windowBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
index d8b617a60129..c937d5c6453d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
@@ -64,4 +64,6 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio
},
onFinish = { blurConfig.maxBlurRadiusPx },
)
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
index 597df15a2b55..5ab458334a25 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
@@ -42,4 +42,7 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio
override val windowBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index c373fd01ba20..44c4c8723dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -23,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.BlurConfig
+import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -32,6 +34,7 @@ import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
/**
* Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -70,6 +73,29 @@ constructor(
val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+ val notificationAlpha: Flow<Float> =
+ if (Flags.bouncerUiRevamp()) {
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded = lockscreenAlpha,
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(1f),
+ )
+ } else {
+ lockscreenAlpha
+ }
+
+ override val notificationBlurRadius: Flow<Float> =
+ if (Flags.bouncerUiRevamp()) {
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded = emptyFlow(),
+ flowWhenShadeIsExpanded =
+ transitionAnimation.immediatelyTransitionTo(
+ blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius()
+ ),
+ )
+ } else {
+ emptyFlow()
+ }
+
override val deviceEntryParentViewAlpha: Flow<Float> =
shadeDependentFlows.transitionFlow(
flowWhenShadeIsNotExpanded =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
index 44598107fa4b..4d3e27265cea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
@@ -42,4 +42,7 @@ constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFl
override val windowBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
index fab8008cbfa7..224191b64f5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -91,4 +91,7 @@ constructor(
},
onFinish = { blurConfig.minBlurRadiusPx },
)
+
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
index eebdf2ef418e..0f8495f34d22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
@@ -80,4 +80,6 @@ constructor(
},
onFinish = { blurConfig.minBlurRadiusPx },
)
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
index 3636b747d5c9..a13eef2388f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
@@ -43,4 +43,7 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio
override val windowBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
+
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 4ed3e6cde230..d1233f220f47 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -166,6 +166,9 @@ constructor(
createBouncerWindowBlurFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
}
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
+
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, PRIMARY_BOUNCER)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index 2edc93cb5617..c53a408a88e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -91,4 +91,7 @@ constructor(
},
),
)
+
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
index 3a54a26858d4..fe1708efea2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
@@ -42,4 +42,7 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio
override val windowBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
+
+ override val notificationBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
index 09544827a51a..a6b9442b1270 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -31,6 +31,7 @@ import androidx.media3.session.CommandButton
import androidx.media3.session.MediaController as Media3Controller
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionToken
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -128,7 +129,11 @@ constructor(
drawable,
null, // no action to perform when clicked
context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_connecting_status_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_connecting_container)
+ },
// Specify a rebind id to prevent the spinner from restarting on later binds.
com.android.internal.R.drawable.progress_small_material,
)
@@ -230,17 +235,33 @@ constructor(
Player.COMMAND_PLAY_PAUSE -> {
if (!controller.isPlaying) {
MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play)
+ },
{ executeAction(packageName, token, Player.COMMAND_PLAY_PAUSE) },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play_container)
+ },
)
} else {
MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_pause_button)
+ } else {
+ context.getDrawable(R.drawable.ic_media_pause)
+ },
{ executeAction(packageName, token, Player.COMMAND_PLAY_PAUSE) },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_pause_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_pause_container)
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
index 4f9791353b8a..9bf556cf07c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -29,6 +29,7 @@ import android.media.session.PlaybackState
import android.service.notification.StatusBarNotification
import android.util.Log
import androidx.media.utils.MediaConstants
+import com.android.systemui.Flags
import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
import com.android.systemui.media.controls.shared.MediaControlDrawables
@@ -69,7 +70,11 @@ fun createActionsFromState(
drawable,
null, // no action to perform when clicked
context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_connecting_status_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_connecting_container)
+ },
// Specify a rebind id to prevent the spinner from restarting on later binds.
com.android.internal.R.drawable.progress_small_material,
)
@@ -157,18 +162,34 @@ private fun getStandardAction(
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play)
+ },
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_play_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_play_container)
+ },
)
}
PlaybackState.ACTION_PAUSE -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_pause_button)
+ } else {
+ context.getDrawable(R.drawable.ic_media_pause)
+ },
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container),
+ if (Flags.mediaControlsUiUpdate()) {
+ context.getDrawable(R.drawable.ic_media_pause_button_container)
+ } else {
+ context.getDrawable(R.drawable.ic_media_pause_container)
+ },
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
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 3928a711f840..a2ddc20844e7 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
@@ -1016,9 +1016,24 @@ constructor(
expandedLayout.load(context, R.xml.media_recommendations_expanded)
}
}
+ readjustPlayPauseWidth()
refreshState()
}
+ private fun readjustPlayPauseWidth() {
+ // TODO: move to xml file when flag is removed.
+ if (Flags.mediaControlsUiUpdate()) {
+ collapsedLayout.constrainWidth(
+ R.id.actionPlayPause,
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_action_play_pause_width),
+ )
+ expandedLayout.constrainWidth(
+ R.id.actionPlayPause,
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_action_play_pause_width),
+ )
+ }
+ }
+
/** Get a view state based on the width and height set by the scene */
private fun obtainSceneContainerViewState(state: MediaHostState?): TransitionViewState? {
logger.logMediaSize("scene container", widthInSceneContainerPx, heightInSceneContainerPx)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index ec8d30b01eab..e93cec875429 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -41,12 +41,14 @@ import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.qs.TileDetailsViewModel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.dialog.ScreenRecordDetailsViewModel;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.screenrecord.data.model.ScreenRecordModel;
@@ -54,6 +56,8 @@ import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import java.util.function.Consumer;
+
import javax.inject.Inject;
/**
@@ -122,17 +126,78 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
@Override
protected void handleClick(@Nullable Expandable expandable) {
+ handleClick(() -> showDialog(expandable));
+ }
+
+ private void showDialog(@Nullable Expandable expandable) {
+ final Dialog dialog = mController.createScreenRecordDialog(
+ this::onStartRecordingClicked);
+
+ executeWhenUnlockedKeyguard(() -> {
+ // We animate from the touched view only if we are not on the keyguard, given that if we
+ // are we will dismiss it which will also collapse the shade.
+ boolean shouldAnimateFromExpandable =
+ expandable != null && !mKeyguardStateController.isShowing();
+
+ if (shouldAnimateFromExpandable) {
+ DialogTransitionAnimator.Controller controller =
+ expandable.dialogTransitionController(new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
+ if (controller != null) {
+ mDialogTransitionAnimator.show(dialog,
+ controller, /* animateBackgroundBoundsChange= */ true);
+ } else {
+ dialog.show();
+ }
+ } else {
+ dialog.show();
+ }
+ });
+ }
+
+ private void onStartRecordingClicked() {
+ // We dismiss the shade. Since starting the recording will also dismiss the dialog (if
+ // there is one showing), we disable the exit animation which looks weird when it happens
+ // at the same time as the shade collapsing.
+ mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
+ mPanelInteractor.collapsePanels();
+ }
+
+ private void executeWhenUnlockedKeyguard(Runnable dismissActionCallback) {
+ ActivityStarter.OnDismissAction dismissAction = () -> {
+ dismissActionCallback.run();
+
+ int uid = mUserContextProvider.getUserContext().getUserId();
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
+
+ return false;
+ };
+
+ mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */,
+ true /* afterKeyguardDone */);
+ }
+
+ private void handleClick(Runnable showPromptCallback) {
if (mController.isStarting()) {
cancelCountdown();
} else if (mController.isRecording()) {
stopRecording();
} else {
- mUiHandler.post(() -> showPrompt(expandable));
+ mUiHandler.post(showPromptCallback);
}
refreshState();
}
@Override
+ public boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) {
+ handleClick(() ->
+ callback.accept(new ScreenRecordDetailsViewModel())
+ );
+ return true;
+ }
+
+ @Override
protected void handleUpdateState(BooleanState state, Object arg) {
boolean isStarting = mController.isStarting();
boolean isRecording = mController.isRecording();
@@ -178,49 +243,6 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
return mContext.getString(R.string.quick_settings_screen_record_label);
}
- private void showPrompt(@Nullable Expandable expandable) {
- // We animate from the touched view only if we are not on the keyguard, given that if we
- // are we will dismiss it which will also collapse the shade.
- boolean shouldAnimateFromExpandable =
- expandable != null && !mKeyguardStateController.isShowing();
-
- // Create the recording dialog that will collapse the shade only if we start the recording.
- Runnable onStartRecordingClicked = () -> {
- // We dismiss the shade. Since starting the recording will also dismiss the dialog, we
- // disable the exit animation which looks weird when it happens at the same time as the
- // shade collapsing.
- mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
- mPanelInteractor.collapsePanels();
- };
-
- final Dialog dialog = mController.createScreenRecordDialog(onStartRecordingClicked);
-
- ActivityStarter.OnDismissAction dismissAction = () -> {
- if (shouldAnimateFromExpandable) {
- DialogTransitionAnimator.Controller controller =
- expandable.dialogTransitionController(new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG));
- if (controller != null) {
- mDialogTransitionAnimator.show(dialog,
- controller, /* animateBackgroundBoundsChange= */ true);
- } else {
- dialog.show();
- }
- } else {
- dialog.show();
- }
-
- int uid = mUserContextProvider.getUserContext().getUserId();
- mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
-
- return false;
- };
-
- mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */,
- true /* afterKeyguardDone */);
- }
-
private void cancelCountdown() {
Log.d(TAG, "Cancelling countdown");
mController.cancelCountdown();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
new file mode 100644
index 000000000000..42cb1248ccff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.res.R
+
+/** The view model used for the screen record details view in the Quick Settings */
+class ScreenRecordDetailsViewModel() : TileDetailsViewModel() {
+ @Composable
+ override fun GetContentView() {
+ // TODO(b/378514312): Finish implementing this function.
+ AndroidView(
+ modifier = Modifier.fillMaxWidth().heightIn(max = VIEW_MAX_HEIGHT),
+ factory = { context ->
+ // Inflate with the existing dialog xml layout
+ LayoutInflater.from(context).inflate(R.layout.screen_share_dialog, null)
+ },
+ )
+ }
+
+ override fun clickOnSettingsButton() {
+ // No settings button in this tile.
+ }
+
+ override fun getTitle(): String {
+ // TODO(b/388321032): Replace this string with a string in a translatable xml file,
+ return "Screen recording"
+ }
+
+ override fun getSubTitle(): String {
+ // No sub-title in this tile.
+ return ""
+ }
+
+ companion object {
+ private val VIEW_MAX_HEIGHT: Dp = 320.dp
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 19152170757c..c4306d3f7530 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -109,6 +109,7 @@ import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
+import com.android.systemui.keyguard.ui.transitions.BlurConfig;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -914,13 +915,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
if (!com.android.systemui.Flags.bouncerUiRevamp()) return;
if (isBouncerShowing && isExpanded()) {
- // Blur the shade much lesser than the background surface so that the surface is
- // distinguishable from the background.
- float shadeBlurEffect = mDepthController.getMaxBlurRadiusPx() / 3;
+ float shadeBlurEffect = BlurConfig.maxBlurRadiusToNotificationPanelBlurRadius(
+ mDepthController.getMaxBlurRadiusPx());
mView.setRenderEffect(RenderEffect.createBlurEffect(
shadeBlurEffect,
shadeBlurEffect,
- Shader.TileMode.MIRROR));
+ Shader.TileMode.CLAMP));
} else {
mView.setRenderEffect(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
index 4d35d0eba178..e358dcec8b10 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
@@ -24,7 +24,6 @@ import com.android.systemui.common.ui.view.ChoreographerUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker.Companion.TIMEOUT
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
@@ -33,7 +32,6 @@ import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
@@ -135,7 +133,7 @@ constructor(
private companion object {
const val TAG = "ShadeDisplayLatency"
- val t = TrackTracer(trackName = TAG)
+ val t = TrackTracer(trackName = TAG, trackGroup = "shade")
val TIMEOUT = 3.seconds
const val SHADE_MOVE_ACTION = LatencyTracker.ACTION_SHADE_WINDOW_DISPLAY_CHANGE
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 359ddd86f115..5fab889735a6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -18,13 +18,16 @@ package com.android.systemui.shade
import android.annotation.IntDef
import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP as TRACE_TAG
import android.util.Log
import androidx.annotation.FloatRange
+import com.android.app.tracing.TraceStateLogger
+import com.android.app.tracing.TrackGroupUtils.trackGroup
+import com.android.app.tracing.coroutines.TrackTracer
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.Compile
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
-import android.os.Trace.TRACE_TAG_APP as TRACE_TAG
/**
* A class responsible for managing the notification panel's current state.
@@ -38,6 +41,8 @@ class ShadeExpansionStateManager @Inject constructor() {
private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
+ private val stateLogger = TraceStateLogger(trackGroup("shade", TRACK_NAME))
+
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
private var expanded: Boolean = false
@@ -75,7 +80,7 @@ class ShadeExpansionStateManager @Inject constructor() {
fun onPanelExpansionChanged(
@FloatRange(from = 0.0, to = 1.0) fraction: Float,
expanded: Boolean,
- tracking: Boolean
+ tracking: Boolean,
) {
require(!fraction.isNaN()) { "fraction cannot be NaN" }
val oldState = state
@@ -113,11 +118,8 @@ class ShadeExpansionStateManager @Inject constructor() {
)
if (Trace.isTagEnabled(TRACE_TAG)) {
- Trace.traceCounter(TRACE_TAG, "panel_expansion", (fraction * 100).toInt())
- if (state != oldState) {
- Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK_NAME, 0)
- Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK_NAME, state.panelStateToString(), 0)
- }
+ TrackTracer.instantForGroup("shade", "panel_expansion", fraction)
+ stateLogger.log(state.panelStateToString())
}
val expansionChangeEvent = ShadeExpansionChangeEvent(fraction, expanded, tracking)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
new file mode 100644
index 000000000000..2705cdafb4de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
@@ -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 com.android.systemui.shade
+
+import com.android.app.tracing.TraceStateLogger
+import com.android.app.tracing.TrackGroupUtils.trackGroup
+import com.android.app.tracing.coroutines.TrackTracer.Companion.instantForGroup
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class ShadeStateTraceLogger
+@Inject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ private val shadeDisplaysRepository: ShadeDisplaysRepository,
+ @Application private val scope: CoroutineScope,
+) : CoreStartable {
+ override fun start() {
+ scope.launchTraced("ShadeStateTraceLogger") {
+ launch {
+ val stateLogger = createTraceStateLogger("isShadeLayoutWide")
+ shadeInteractor.isShadeLayoutWide.collect { stateLogger.log(it.toString()) }
+ }
+ launch {
+ val stateLogger = createTraceStateLogger("shadeMode")
+ shadeInteractor.shadeMode.collect { stateLogger.log(it.toString()) }
+ }
+ launch {
+ shadeInteractor.shadeExpansion.collect {
+ instantForGroup(TRACK_GROUP_NAME, "shadeExpansion", it)
+ }
+ }
+ launch {
+ shadeDisplaysRepository.displayId.collect {
+ instantForGroup(TRACK_GROUP_NAME, "displayId", it)
+ }
+ }
+ }
+ }
+
+ private fun createTraceStateLogger(trackName: String): TraceStateLogger {
+ return TraceStateLogger(trackGroup(TRACK_GROUP_NAME, trackName))
+ }
+
+ private companion object {
+ const val TRACK_GROUP_NAME = "shade"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt
index a36c56eafbfc..11805992fd6a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt
@@ -27,7 +27,7 @@ import com.android.app.tracing.coroutines.TrackTracer
* them across various threads' logs.
*/
object ShadeTraceLogger {
- private val t = TrackTracer(trackName = "ShadeTraceLogger")
+ private val t = TrackTracer(trackName = "ShadeTraceLogger", trackGroup = "shade")
@JvmStatic
fun logOnMovedToDisplay(displayId: Int, config: Configuration) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
index c4de78b8a28e..570a7853c394 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
@@ -40,4 +40,9 @@ internal abstract class StartShadeModule {
@IntoMap
@ClassKey(ShadeStartable::class)
abstract fun provideShadeStartable(startable: ShadeStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(ShadeStateTraceLogger::class)
+ abstract fun provideShadeStateTraceLogger(startable: ShadeStateTraceLogger): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 37989f56d559..2885ce80bda9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -11,13 +11,13 @@ import android.graphics.PorterDuffColorFilter
import android.graphics.PorterDuffXfermode
import android.graphics.RadialGradient
import android.graphics.Shader
-import android.os.Trace
import android.util.AttributeSet
import android.util.MathUtils.lerp
import android.view.MotionEvent
import android.view.View
import android.view.animation.PathInterpolator
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.TrackTracer
import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.shade.TouchLogger
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
@@ -321,9 +321,8 @@ constructor(
}
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
- Trace.traceCounter(
- Trace.TRACE_TAG_APP,
- "light_reveal_amount $logString",
+ TrackTracer.instantForGroup(
+ "scrim", { "light_reveal_amount $logString" },
(field * 100).toInt()
)
invalidate()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index e83cded4e2ce..75117936c090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -22,7 +22,6 @@ import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.os.SystemClock
-import android.os.Trace
import android.util.IndentingPrintWriter
import android.util.Log
import android.util.MathUtils
@@ -33,6 +32,7 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.TrackTracer
import com.android.systemui.Dumpable
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
@@ -263,7 +263,7 @@ constructor(
updateScheduled = false
val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut()
val opaque = shouldBlurBeOpaque
- Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", blur)
+ TrackTracer.instantForGroup("shade", "shade_blur_radius", blur)
blurUtils.applyBlur(root.viewRootImpl, blur, opaque)
onBlurApplied(blur, zoomOutFromShadeRadius)
}
@@ -384,7 +384,7 @@ constructor(
windowRootViewBlurInteractor.onBlurAppliedEvent.collect { appliedBlurRadius ->
if (updateScheduled) {
// Process the blur applied event only if we scheduled the update
- Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", appliedBlurRadius)
+ TrackTracer.instantForGroup("shade", "shade_blur_radius", appliedBlurRadius)
updateScheduled = false
onBlurApplied(appliedBlurRadius, zoomOutCalculatedFromShadeRadius)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index a7ad46296e08..ead8f6a1123e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -37,6 +37,7 @@ import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import com.android.app.animation.Interpolators;
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.compose.animation.scene.OverlayKey;
import com.android.compose.animation.scene.SceneKey;
import com.android.internal.annotations.GuardedBy;
@@ -671,7 +672,7 @@ public class StatusBarStateControllerImpl implements
}
private void recordHistoricalState(int newState, int lastState, boolean upcoming) {
- Trace.traceCounter(Trace.TRACE_TAG_APP, "statusBarState", newState);
+ TrackTracer.instantForGroup("statusBar", "state", newState);
mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
HistoricalState state = mHistoricalRecords[mHistoryIndex];
state.mNewState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
index 90212ed5b5f7..034a4fd2af72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
@@ -36,7 +36,7 @@ class DataStoreCoordinator
internal constructor(private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl) : CoreCoordinator {
override fun attach(pipeline: NotifPipeline) {
- pipeline.addOnAfterRenderListListener { entries, _ -> onAfterRenderList(entries) }
+ pipeline.addOnAfterRenderListListener { entries -> onAfterRenderList(entries) }
}
override fun dumpPipeline(d: PipelineDumper) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index d4d3cdf42fb1..1cb2366a16fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -23,8 +23,7 @@ import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
@@ -51,8 +50,7 @@ internal constructor(
groupExpansionManagerImpl.attach(pipeline)
}
- // TODO: b/293167744 - Remove controller param.
- private fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
+ private fun onAfterRenderList(entries: List<ListEntry>) =
traceSection("StackCoordinator.onAfterRenderList") {
val notifStats = calculateNotifStats(entries)
activeNotificationsInteractor.setNotifStats(notifStats)
@@ -84,7 +82,6 @@ internal constructor(
}
}
return NotifStats(
- numActiveNotifs = entries.size,
hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs,
hasClearableAlertingNotifs = hasClearableAlertingNotifs,
hasNonClearableSilentNotifs = hasNonClearableSilentNotifs,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index a34d033afcaa..c58b3febe54b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -33,7 +33,6 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.collection.render.RenderStageManager;
import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory;
@@ -89,8 +88,7 @@ public class NotifPipelineInitializer implements Dumpable, PipelineDumpable {
public void initialize(
NotificationListener notificationService,
NotificationRowBinderImpl rowBinder,
- NotificationListContainer listContainer,
- NotifStackController stackController) {
+ NotificationListContainer listContainer) {
mDumpManager.registerDumpable("NotifPipeline", this);
mNotificationService = notificationService;
@@ -102,7 +100,7 @@ public class NotifPipelineInitializer implements Dumpable, PipelineDumpable {
mNotifPluggableCoordinators.attach(mPipelineWrapper);
// Wire up pipeline
- mShadeViewManager = mShadeViewManagerFactory.create(listContainer, stackController);
+ mShadeViewManager = mShadeViewManagerFactory.create(listContainer);
mShadeViewManager.attach(mRenderStageManager);
mRenderStageManager.attach(mListBuilder);
mListBuilder.attach(mNotifCollection);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
index b5a0f7ae169d..ac450c03b850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
@@ -20,7 +20,6 @@ import androidx.annotation.NonNull;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import java.util.List;
@@ -31,9 +30,6 @@ public interface OnAfterRenderListListener {
*
* @param entries The current list of top-level entries. Note that this is a live view into the
* current list and will change whenever the pipeline is rerun.
- * @param controller An object for setting state on the shade.
*/
- void onAfterRenderList(
- @NonNull List<ListEntry> entries,
- @NonNull NotifStackController controller);
+ void onAfterRenderList(@NonNull List<ListEntry> entries);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
deleted file mode 100644
index a37937a6c495..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import javax.inject.Inject
-
-/** An interface by which the pipeline can make updates to the notification root view. */
-interface NotifStackController {
- /** Provides stats about the list of notifications attached to the shade */
- fun setNotifStats(stats: NotifStats)
-}
-
-/** Data provided to the NotificationRootController whenever the pipeline runs */
-data class NotifStats(
- // TODO(b/293167744): The count can be removed from here when we remove the FooterView flag.
- val numActiveNotifs: Int,
- val hasNonClearableAlertingNotifs: Boolean,
- val hasClearableAlertingNotifs: Boolean,
- val hasNonClearableSilentNotifs: Boolean,
- val hasClearableSilentNotifs: Boolean
-) {
- companion object {
- @JvmStatic val empty = NotifStats(0, false, false, false, false)
- }
-}
-
-/**
- * An implementation of NotifStackController which provides default, no-op implementations of each
- * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding methods,
- * rather than forcing us to add no-op implementations in their implementation every time a method
- * is added.
- */
-open class DefaultNotifStackController @Inject constructor() : NotifStackController {
- override fun setNotifStats(stats: NotifStats) {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
index 410b78b9d3bf..8284022c7270 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
@@ -37,12 +37,6 @@ interface NotifViewRenderer {
fun onRenderList(notifList: List<ListEntry>)
/**
- * Provides an interface for the pipeline to update the overall shade. This will be called at
- * most once for each time [onRenderList] is called.
- */
- fun getStackController(): NotifStackController
-
- /**
* Provides an interface for the pipeline to update individual groups. This will be called at
* most once for each group in the most recent call to [onRenderList].
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
index 9d3b098fa966..21e68376031c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -50,7 +50,7 @@ class RenderStageManager @Inject constructor() : PipelineDumpable {
traceSection("RenderStageManager.onRenderList") {
val viewRenderer = viewRenderer ?: return
viewRenderer.onRenderList(notifList)
- dispatchOnAfterRenderList(viewRenderer, notifList)
+ dispatchOnAfterRenderList(notifList)
dispatchOnAfterRenderGroups(viewRenderer, notifList)
dispatchOnAfterRenderEntries(viewRenderer, notifList)
viewRenderer.onDispatchComplete()
@@ -85,15 +85,9 @@ class RenderStageManager @Inject constructor() : PipelineDumpable {
dump("onAfterRenderEntryListeners", onAfterRenderEntryListeners)
}
- private fun dispatchOnAfterRenderList(
- viewRenderer: NotifViewRenderer,
- entries: List<ListEntry>,
- ) {
+ private fun dispatchOnAfterRenderList(entries: List<ListEntry>) {
traceSection("RenderStageManager.dispatchOnAfterRenderList") {
- val stackController = viewRenderer.getStackController()
- onAfterRenderListListeners.forEach { listener ->
- listener.onAfterRenderList(entries, stackController)
- }
+ onAfterRenderListListeners.forEach { listener -> listener.onAfterRenderList(entries) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 3c838e5b707e..72316bf14c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -41,7 +41,6 @@ class ShadeViewManager
constructor(
@ShadeDisplayAware context: Context,
@Assisted listContainer: NotificationListContainer,
- @Assisted private val stackController: NotifStackController,
mediaContainerController: MediaContainerController,
featureManager: NotificationSectionsFeatureManager,
sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
@@ -83,8 +82,6 @@ constructor(
}
}
- override fun getStackController(): NotifStackController = stackController
-
override fun getGroupController(group: GroupEntry): NotifGroupController =
viewBarn.requireGroupController(group.requireSummary)
@@ -95,8 +92,5 @@ constructor(
@AssistedFactory
interface ShadeViewManagerFactory {
- fun create(
- listContainer: NotificationListContainer,
- stackController: NotifStackController,
- ): ShadeViewManager
+ fun create(listContainer: NotificationListContainer): ShadeViewManager
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/model/NotifStats.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/model/NotifStats.kt
new file mode 100644
index 000000000000..d7fd7025a94f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/model/NotifStats.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data.model
+
+/** Information about the current list of notifications. */
+data class NotifStats(
+ val hasNonClearableAlertingNotifs: Boolean,
+ val hasClearableAlertingNotifs: Boolean,
+ val hasNonClearableSilentNotifs: Boolean,
+ val hasClearableSilentNotifs: Boolean,
+) {
+ companion object {
+ @JvmStatic
+ val empty =
+ NotifStats(
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = false,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 2b9e49372a63..70f06ebe8468 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 6b93ee1c435e..0c040c855368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index 2c5d9c2e449b..3c2051f0b153 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -20,7 +20,6 @@ import android.service.notification.StatusBarNotification
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.NotificationActivityStarter
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
/**
@@ -33,7 +32,6 @@ interface NotificationsController {
fun initialize(
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
- stackController: NotifStackController,
notificationActivityStarter: NotificationActivityStarter,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index ea6a60bd7a1c..0a9899e88d24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -34,7 +34,6 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica
import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
@@ -76,7 +75,6 @@ constructor(
override fun initialize(
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
- stackController: NotifStackController,
notificationActivityStarter: NotificationActivityStarter,
) {
notificationListener.registerAsSystemService()
@@ -101,7 +99,7 @@ constructor(
notifPipelineInitializer
.get()
- .initialize(notificationListener, notificationRowBinder, listContainer, stackController)
+ .initialize(notificationListener, notificationRowBinder, listContainer)
targetSdkResolver.initialize(notifPipeline.get())
notificationsMediaManager.setUpWithPresenter(presenter)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index 148b3f021643..92d96f9e899b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -21,7 +21,6 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.NotificationActivityStarter
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import javax.inject.Inject
@@ -35,7 +34,6 @@ constructor(private val notificationListener: NotificationListener) : Notificati
override fun initialize(
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
- stackController: NotifStackController,
notificationActivityStarter: NotificationActivityStarter,
) {
// Always connect the listener even if notification-handling is disabled. Being a listener
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index c6832bc20e6d..cc4be57168cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -20,7 +20,6 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.Trace;
import android.service.notification.NotificationListenerService;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -29,6 +28,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
@@ -152,8 +152,8 @@ public class NotificationLogger implements StateListener, CoreStartable,
mExpansionStateLogger.onVisibilityChanged(
mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications);
- Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Active]", N);
- Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Visible]",
+ TrackTracer.instantForGroup("Notifications", "Active", N);
+ TrackTracer.instantForGroup("Notifications", "Visible",
mCurrentlyVisibleNotifications.size());
recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
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 7e3d0043b91a..95604c113a15 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
@@ -1267,6 +1267,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
if (mExpandedWhenPinned) {
return Math.max(getMaxExpandHeight(), getHeadsUpHeight());
+ } else if (android.app.Flags.compactHeadsUpNotification()
+ && getShowingLayout().isHUNCompact()) {
+ return getHeadsUpHeight();
} else if (atLeastMinHeight) {
return Math.max(getCollapsedHeight(), getHeadsUpHeight());
} else {
@@ -3680,6 +3683,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return super.disallowSingleClick(event);
}
+ // TODO: b/388470175 - Although this does get triggered when a notification
+ // is expanded by the system (e.g. the first notication in the shade), it
+ // will not be when a notification is collapsed by the system (such as when
+ // the shade is closed).
private void onExpansionChanged(boolean userAction, boolean wasExpanded) {
boolean nowExpanded = isExpanded();
if (mIsSummaryWithChildren && (!mIsMinimized || wasExpanded)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 786d7d9ea0f3..0d2998174121 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -207,6 +207,8 @@ public class NotificationContentView extends FrameLayout implements Notification
private boolean mContentAnimating;
private UiEventLogger mUiEventLogger;
+ private boolean mIsHUNCompact;
+
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext());
@@ -543,6 +545,7 @@ public class NotificationContentView extends FrameLayout implements Notification
if (child == null) {
mHeadsUpChild = null;
mHeadsUpWrapper = null;
+ mIsHUNCompact = false;
if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
}
@@ -556,8 +559,9 @@ public class NotificationContentView extends FrameLayout implements Notification
mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
- if (Flags.compactHeadsUpNotification()
- && mHeadsUpWrapper instanceof NotificationCompactHeadsUpTemplateViewWrapper) {
+ mIsHUNCompact = Flags.compactHeadsUpNotification()
+ && mHeadsUpWrapper instanceof NotificationCompactHeadsUpTemplateViewWrapper;
+ if (mIsHUNCompact) {
logCompactHUNShownEvent();
}
@@ -902,6 +906,10 @@ public class NotificationContentView extends FrameLayout implements Notification
}
}
+ public boolean isHUNCompact() {
+ return mIsHUNCompact;
+ }
+
private boolean isGroupExpanded() {
return mContainingNotification.isGroupExpanded();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index b892bebb3120..c717e3b229be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -35,6 +35,8 @@ import static com.android.systemui.statusbar.notification.stack.StackStateAnimat
import android.animation.ObjectAnimator;
import android.content.res.Configuration;
import android.graphics.Point;
+import android.graphics.RenderEffect;
+import android.graphics.Shader;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
@@ -103,9 +105,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
-import com.android.systemui.statusbar.notification.collection.render.DefaultNotifStackController;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl;
@@ -211,9 +211,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final NotificationListContainerImpl mNotificationListContainer =
new NotificationListContainerImpl();
- // TODO: b/293167744 - Remove this.
- private final NotifStackController mNotifStackController =
- new DefaultNotifStackController();
@VisibleForTesting
final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
@@ -1242,6 +1239,22 @@ public class NotificationStackScrollLayoutController implements Dumpable {
updateAlpha();
}
+ /**
+ * Applies a blur effect to the view.
+ *
+ * @param blurRadius Radius of blur
+ */
+ public void setBlurRadius(float blurRadius) {
+ if (blurRadius > 0.0f) {
+ mView.setRenderEffect(RenderEffect.createBlurEffect(
+ blurRadius,
+ blurRadius,
+ Shader.TileMode.CLAMP));
+ } else {
+ mView.setRenderEffect(null);
+ }
+ }
+
private void updateAlpha() {
if (mView != null) {
mView.setAlpha(Math.min(Math.min(mMaxAlphaFromView, mMaxAlphaForKeyguard),
@@ -1469,10 +1482,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mNotificationListContainer;
}
- public NotifStackController getNotifStackController() {
- return mNotifStackController;
- }
-
public void resetCheckSnoozeLeavebehind() {
mView.resetCheckSnoozeLeavebehind();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
index 53749ff24394..c8c798d00a06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.stack.ui.view
-import android.os.Trace
import android.service.notification.NotificationListenerService
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.TrackTracer
import com.android.internal.statusbar.IStatusBarService
import com.android.internal.statusbar.NotificationVisibility
import com.android.systemui.dagger.SysUISingleton
@@ -183,8 +183,8 @@ constructor(
maybeLogVisibilityChanges(newlyVisible, noLongerVisible, activeNotifCount)
updateExpansionStates(newlyVisible, noLongerVisible)
- Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Active]", activeNotifCount)
- Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Visible]", newVisibilities.size)
+ TrackTracer.instantForGroup("Notifications", "Active", activeNotifCount)
+ TrackTracer.instantForGroup("Notifications", "Visible", newVisibilities.size)
lastLoggedVisibilities.clear()
lastLoggedVisibilities.putAll(newVisibilities)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 0b2b84e60f4b..3ea4d488357d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -179,6 +179,10 @@ constructor(
}
}
+ if (Flags.bouncerUiRevamp()) {
+ launch { viewModel.blurRadius.collect { controller.setBlurRadius(it) } }
+ }
+
if (communalSettingsInteractor.isCommunalFlagEnabled()) {
launch {
viewModel.glanceableHubAlpha.collect {
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 fc8c70fb8e9a..f0455fc3a22b 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
@@ -42,6 +42,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+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
@@ -154,6 +155,7 @@ constructor(
private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
private val primaryBouncerToLockscreenTransitionViewModel:
PrimaryBouncerToLockscreenTransitionViewModel,
+ private val primaryBouncerTransitions: Set<@JvmSuppressWildcards PrimaryBouncerTransition>,
aodBurnInViewModel: AodBurnInViewModel,
private val communalSceneInteractor: CommunalSceneInteractor,
// Lazy because it's only used in the SceneContainer + Dual Shade configuration.
@@ -562,7 +564,7 @@ constructor(
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
- lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
+ lockscreenToPrimaryBouncerTransitionViewModel.notificationAlpha,
alternateBouncerToPrimaryBouncerTransitionViewModel.notificationAlpha,
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToGoneTransitionViewModel.notificationAlpha(viewState),
@@ -626,6 +628,12 @@ constructor(
.dumpWhileCollecting("keyguardAlpha")
}
+ val blurRadius =
+ primaryBouncerTransitions
+ .map { transition -> transition.notificationBlurRadius }
+ .merge()
+ .dumpWhileCollecting("blurRadius")
+
/**
* Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB or
* DREAMING<->GLANCEABLE_HUB transition or idle on the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3d6cd7e49dfe..b146b92ed110 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1492,7 +1492,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mNotificationsController.initialize(
mPresenterLazy.get(),
mNotifListContainer,
- mStackScrollerController.getNotifStackController(),
mNotificationActivityStarterLazy.get());
mWindowRootViewVisibilityInteractor.setUp(mPresenterLazy.get(), mNotificationsController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 324db79a4078..d43fed0cbf59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -43,6 +43,7 @@ import android.view.animation.Interpolator;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.graphics.ColorUtils;
@@ -554,7 +555,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
final ScrimState oldState = mState;
mState = state;
- Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal());
+ TrackTracer.instantForGroup("scrim", "state", mState.ordinal());
if (mCallback != null) {
mCallback.onCancelled();
@@ -1279,10 +1280,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
tint = getDebugScrimTint(scrimView);
}
- Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
+ TrackTracer.instantForGroup("scrim", getScrimName(scrimView) + "_alpha",
(int) (alpha * 255));
-
- Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
+ TrackTracer.instantForGroup("scrim", getScrimName(scrimView) + "_tint",
Color.alpha(tint));
scrimView.setTint(tint);
if (!mIsBouncerToGoneTransitionRunning) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 198859a9013d..8dcb66312558 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.phone;
import android.graphics.Color;
-import android.os.Trace;
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.systemui.dock.DockManager;
import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
@@ -425,11 +425,11 @@ public enum ScrimState {
tint = scrim == mScrimInFront ? ScrimController.DEBUG_FRONT_TINT
: ScrimController.DEBUG_BEHIND_TINT;
}
- Trace.traceCounter(Trace.TRACE_TAG_APP,
+ TrackTracer.instantForGroup("scrim",
scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
(int) (alpha * 255));
- Trace.traceCounter(Trace.TRACE_TAG_APP,
+ TrackTracer.instantForGroup("scrim",
scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
Color.alpha(tint));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index c31e34c50b06..e622d8f52894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -81,6 +81,7 @@ import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
@@ -142,6 +143,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private StatusBarVisibilityModel mLastModifiedVisibility =
StatusBarVisibilityModel.createDefaultModel();
private DarkIconManager mDarkIconManager;
+ private HomeStatusBarViewModel mHomeStatusBarViewModel;
+
private final HomeStatusBarComponent.Factory mHomeStatusBarComponentFactory;
private final CommandQueue mCommandQueue;
private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
@@ -151,8 +154,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
private final CarrierConfigTracker mCarrierConfigTracker;
- private final HomeStatusBarViewModel mHomeStatusBarViewModel;
private final HomeStatusBarViewBinder mHomeStatusBarViewBinder;
+ private final HomeStatusBarViewModelFactory mHomeStatusBarViewModelFactory;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final DarkIconManager.Factory mDarkIconManagerFactory;
private final SecureSettings mSecureSettings;
@@ -256,7 +259,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
ShadeExpansionStateManager shadeExpansionStateManager,
StatusBarIconController statusBarIconController,
DarkIconManager.Factory darkIconManagerFactory,
- HomeStatusBarViewModel homeStatusBarViewModel,
+ HomeStatusBarViewModelFactory homeStatusBarViewModelFactory,
HomeStatusBarViewBinder homeStatusBarViewBinder,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
KeyguardStateController keyguardStateController,
@@ -281,7 +284,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mAnimationScheduler = animationScheduler;
mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarIconController = statusBarIconController;
- mHomeStatusBarViewModel = homeStatusBarViewModel;
+ mHomeStatusBarViewModelFactory = homeStatusBarViewModelFactory;
mHomeStatusBarViewBinder = homeStatusBarViewBinder;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mDarkIconManagerFactory = darkIconManagerFactory;
@@ -410,6 +413,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
+ mHomeStatusBarViewModel = mHomeStatusBarViewModelFactory.create(displayId);
mHomeStatusBarViewBinder.bind(
view.getContext().getDisplayId(),
mStatusBar,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 96666d83b39b..c71162a22d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -56,8 +56,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinderImpl
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModelImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModelImpl.HomeStatusBarViewModelFactoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
@@ -148,7 +148,9 @@ abstract class StatusBarPipelineModule {
abstract fun bindCarrierConfigStartable(impl: CarrierConfigCoreStartable): CoreStartable
@Binds
- abstract fun homeStatusBarViewModel(impl: HomeStatusBarViewModelImpl): HomeStatusBarViewModel
+ abstract fun homeStatusBarViewModelFactory(
+ impl: HomeStatusBarViewModelFactoryImpl
+ ): HomeStatusBarViewModelFactory
@Binds
abstract fun homeStatusBarViewBinder(impl: HomeStatusBarViewBinderImpl): HomeStatusBarViewBinder
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 0dd7c8499861..2541d84a5a97 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
@@ -107,10 +107,9 @@ constructor(
}
if (NotificationsLiveDataStoreRefactor.isEnabled) {
- val displayId = view.display.displayId
val lightsOutView: View = view.requireViewById(R.id.notification_lights_out)
launch {
- viewModel.areNotificationsLightsOut(displayId).collect { show ->
+ viewModel.areNotificationsLightsOut.collect { show ->
animateLightsOutView(lightsOutView, show)
}
}
@@ -218,7 +217,7 @@ constructor(
StatusBarOperatorNameViewBinder.bind(
operatorNameView,
viewModel.operatorNameViewModel,
- viewModel::areaTint,
+ viewModel.areaTint,
)
launch {
viewModel.shouldShowOperatorNameView.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt
index b7744d34560d..5dd76f4434f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarOperatorNameViewBinder.kt
@@ -32,19 +32,16 @@ object StatusBarOperatorNameViewBinder {
fun bind(
operatorFrameView: View,
viewModel: StatusBarOperatorNameViewModel,
- areaTint: (Int) -> Flow<StatusBarTintColor>,
+ areaTint: Flow<StatusBarTintColor>,
) {
operatorFrameView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- val displayId = operatorFrameView.display.displayId
-
val operatorNameText =
operatorFrameView.requireViewById<TextView>(R.id.operator_name)
launch { viewModel.operatorName.collect { operatorNameText.text = it } }
launch {
- val tint = areaTint(displayId)
- tint.collect { statusBarTintColors ->
+ areaTint.collect { statusBarTintColors ->
operatorNameText.setTextColor(
statusBarTintColors.tint(operatorNameText.viewBoundsOnScreen())
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index f286a1a148fa..b78e010572c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -53,13 +53,14 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarIco
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory
import javax.inject.Inject
/** Factory to simplify the dependency management for [StatusBarRoot] */
class StatusBarRootFactory
@Inject
constructor(
- private val homeStatusBarViewModel: HomeStatusBarViewModel,
+ private val homeStatusBarViewModelFactory: HomeStatusBarViewModelFactory,
private val homeStatusBarViewBinder: HomeStatusBarViewBinder,
private val notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
private val darkIconManagerFactory: DarkIconManager.Factory,
@@ -70,13 +71,14 @@ constructor(
) {
fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
val composeView = ComposeView(root.context)
+ val displayId = root.context.displayId
val darkIconDispatcher =
darkIconDispatcherStore.forDisplay(root.context.displayId) ?: return composeView
composeView.apply {
setContent {
StatusBarRoot(
parent = root,
- statusBarViewModel = homeStatusBarViewModel,
+ statusBarViewModel = homeStatusBarViewModelFactory.create(displayId),
statusBarViewBinder = homeStatusBarViewBinder,
notificationIconsBinder = notificationIconsBinder,
darkIconManagerFactory = darkIconManagerFactory,
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 c9cc17389c17..3f701fc56ab4 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
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import android.annotation.ColorInt
import android.graphics.Rect
import android.view.View
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -53,7 +52,9 @@ import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteracto
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -118,6 +119,7 @@ interface HomeStatusBarViewModel {
val shouldShowOperatorNameView: Flow<Boolean>
val isClockVisible: Flow<VisibilityModel>
val isNotificationIconContainerVisible: Flow<VisibilityModel>
+
/**
* Pair of (system info visibility, event animation state). The animation state can be used to
* respond to the system event chip animations. In all cases, system info visibility correctly
@@ -137,13 +139,13 @@ interface HomeStatusBarViewModel {
* whether there are notifications when the device is in
* [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE].
*/
- fun areNotificationsLightsOut(displayId: Int): Flow<Boolean>
+ val areNotificationsLightsOut: Flow<Boolean>
/**
- * Given a displayId, returns a flow of [StatusBarTintColor], a functional interface that will
- * allow a view to calculate its correct tint depending on location
+ * A flow of [StatusBarTintColor], a functional interface that will allow a view to calculate
+ * its correct tint depending on location
*/
- fun areaTint(displayId: Int): Flow<StatusBarTintColor>
+ val areaTint: Flow<StatusBarTintColor>
/** Models the current visibility for a specific child view of status bar. */
data class VisibilityModel(
@@ -157,17 +159,22 @@ interface HomeStatusBarViewModel {
val baseVisibility: VisibilityModel,
val animationState: SystemEventAnimationState,
)
+
+ /** Interface for the assisted factory, to allow for providing a fake in tests */
+ interface HomeStatusBarViewModelFactory {
+ fun create(displayId: Int): HomeStatusBarViewModel
+ }
}
-@SysUISingleton
class HomeStatusBarViewModelImpl
-@Inject
+@AssistedInject
constructor(
+ @Assisted thisDisplayId: Int,
homeStatusBarInteractor: HomeStatusBarInteractor,
homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
- private val lightsOutInteractor: LightsOutInteractor,
- private val notificationsInteractor: ActiveNotificationsInteractor,
- private val darkIconInteractor: DarkIconInteractor,
+ lightsOutInteractor: LightsOutInteractor,
+ notificationsInteractor: ActiveNotificationsInteractor,
+ darkIconInteractor: DarkIconInteractor,
headsUpNotificationInteractor: HeadsUpNotificationInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
keyguardInteractor: KeyguardInteractor,
@@ -211,22 +218,22 @@ constructor(
}
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
- override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> =
+ override val areNotificationsLightsOut: Flow<Boolean> =
if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
emptyFlow()
} else {
combine(
notificationsInteractor.areAnyNotificationsPresent,
- lightsOutInteractor.isLowProfile(displayId) ?: flowOf(false),
+ lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
) { hasNotifications, isLowProfile ->
hasNotifications && isLowProfile
}
.distinctUntilChanged()
}
- override fun areaTint(displayId: Int): Flow<StatusBarTintColor> =
+ override val areaTint: Flow<StatusBarTintColor> =
darkIconInteractor
- .darkState(displayId)
+ .darkState(thisDisplayId)
.map { (areas: Collection<Rect>, tint: Int) ->
StatusBarTintColor { viewBounds: Rect ->
if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
@@ -364,6 +371,13 @@ constructor(
// Similar to the above, but uses INVISIBLE in place of GONE
@View.Visibility
private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
+
+ /** Inject this to create the display-dependent view model */
+ @AssistedFactory
+ interface HomeStatusBarViewModelFactoryImpl :
+ HomeStatusBarViewModel.HomeStatusBarViewModelFactory {
+ override fun create(displayId: Int): HomeStatusBarViewModelImpl
+ }
}
/** Lookup the color for a given view in the status bar */
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 1da2491c68b6..46d7d5f680ce 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -24,11 +24,15 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo
+import android.view.WindowInsets
import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.core.view.updatePadding
import com.android.internal.view.RotationPolicy
+import com.android.systemui.common.ui.view.onApplyWindowInsets
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.util.children
+import com.android.systemui.util.kotlin.awaitCancellationThenDispose
import com.android.systemui.volume.SystemUIInterpolators
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
@@ -43,6 +47,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
@@ -71,6 +76,8 @@ constructor(
resources.getInteger(R.integer.config_dialogHideAnimationDurationMs).toLong()
fun CoroutineScope.bind(dialog: Dialog) {
+ val insets: MutableStateFlow<WindowInsets> =
+ MutableStateFlow(WindowInsets.Builder().build())
// Root view of the Volume Dialog.
val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
root.alpha = 0f
@@ -88,6 +95,22 @@ constructor(
launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
+ launch {
+ root
+ .onApplyWindowInsets { v, newInsets ->
+ val insetsValues = newInsets.getInsets(WindowInsets.Type.displayCutout())
+ v.updatePadding(
+ left = insetsValues.left,
+ top = insetsValues.top,
+ right = insetsValues.right,
+ bottom = insetsValues.bottom,
+ )
+ insets.value = newInsets
+ WindowInsets.CONSUMED
+ }
+ .awaitCancellationThenDispose()
+ }
+
with(volumeDialogRingerViewBinder) { bind(root) }
with(slidersViewBinder) { bind(root) }
with(settingsButtonViewBinder) { bind(root) }
@@ -103,8 +126,10 @@ constructor(
when (it) {
is VolumeDialogVisibilityModel.Visible -> {
tracer.traceVisibilityEnd(it)
- calculateTranslationX(view)?.let(view::setTranslationX)
- view.animateShow(dialogShowAnimationDurationMs)
+ view.animateShow(
+ duration = dialogShowAnimationDurationMs,
+ translationX = calculateTranslationX(view),
+ )
}
is VolumeDialogVisibilityModel.Dismissed -> {
tracer.traceVisibilityEnd(it)
@@ -134,24 +159,15 @@ constructor(
}
}
- private suspend fun View.animateShow(duration: Long) {
+ private suspend fun View.animateShow(duration: Long, translationX: Float?) {
+ translationX?.let { setTranslationX(translationX) }
+ alpha = 0f
animate()
.alpha(1f)
.translationX(0f)
.setDuration(duration)
.setInterpolator(SystemUIInterpolators.LogDecelerateInterpolator())
.suspendAnimate(jankListenerFactory.show(this, duration))
- /* TODO(b/369993851)
- .withEndAction(Runnable {
- if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
- if (mRingerIcon != null) {
- mRingerIcon.postOnAnimationDelayed(
- getSinglePressFor(mRingerIcon), 1500
- )
- }
- }
- })
- */
}
private suspend fun View.animateHide(duration: Long, translationX: Float?) {
@@ -160,22 +176,7 @@ constructor(
.alpha(0f)
.setDuration(duration)
.setInterpolator(SystemUIInterpolators.LogAccelerateInterpolator())
- /* TODO(b/369993851)
- .withEndAction(
- Runnable {
- mHandler.postDelayed(
- Runnable {
- hideRingerDrawer()
-
- },
- 50
- )
- }
- )
- */
- if (translationX != null) {
- animator.translationX(translationX)
- }
+ translationX?.let { animator.translationX(it) }
animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 4abbbacd800b..047b78eeb04e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -28,9 +28,11 @@ import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INA
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
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.DOZING
@@ -159,14 +161,12 @@ class ClockEventControllerTest : SysuiTestCase() {
dndModeId = MANUAL_DND_INACTIVE.id
zenModeRepository.addMode(MANUAL_DND_INACTIVE)
- repository = FakeKeyguardRepository()
+ repository = kosmos.fakeKeyguardRepository
- val withDeps = KeyguardInteractorFactory.create(repository = repository)
-
- withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) }
+ kosmos.fakeFeatureFlagsClassic.set(Flags.REGION_SAMPLING, false)
underTest =
ClockEventController(
- withDeps.keyguardInteractor,
+ kosmos.keyguardInteractor,
keyguardTransitionInteractor,
broadcastDispatcher,
batteryController,
@@ -177,7 +177,7 @@ class ClockEventControllerTest : SysuiTestCase() {
mainExecutor,
bgExecutor,
clockBuffers,
- withDeps.featureFlags,
+ kosmos.fakeFeatureFlagsClassic,
zenModeController,
kosmos.zenModeInteractor,
userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
index a3c518128b47..f31d49094ac4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
@@ -26,7 +26,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -48,7 +47,6 @@ class DataStoreCoordinatorTest : SysuiTestCase() {
private val pipeline: NotifPipeline = mock()
private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl = mock()
- private val stackController: NotifStackController = mock()
private val section: NotifSection = mock()
@Before
@@ -63,7 +61,7 @@ class DataStoreCoordinatorTest : SysuiTestCase() {
@Test
fun testUpdateDataStore_withOneEntry() {
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ afterRenderListListener.onAfterRenderList(listOf(entry))
verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf(entry)))
verifyNoMoreInteractions(notifLiveDataStoreImpl)
}
@@ -86,8 +84,7 @@ class DataStoreCoordinatorTest : SysuiTestCase() {
.setSection(section)
.build(),
notificationEntry("baz", 1),
- ),
- stackController,
+ )
)
val list: List<NotificationEntry> = withArgCaptor {
verify(notifLiveDataStoreImpl).setActiveNotifList(capture())
@@ -111,7 +108,7 @@ class DataStoreCoordinatorTest : SysuiTestCase() {
@Test
fun testUpdateDataStore_withZeroEntries_whenNewPipelineEnabled() {
- afterRenderListListener.onAfterRenderList(listOf(), stackController)
+ afterRenderListListener.onAfterRenderList(listOf())
verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf()))
verifyNoMoreInteractions(notifLiveDataStoreImpl)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 77bac59b9dcd..97e99b95f80e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -28,8 +28,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -43,7 +42,6 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -61,7 +59,6 @@ class StackCoordinatorTest : SysuiTestCase() {
private val sensitiveNotificationProtectionController:
SensitiveNotificationProtectionController =
mock()
- private val stackController: NotifStackController = mock()
private val section: NotifSection = mock()
private val row: ExpandableNotificationRow = mock()
@@ -87,25 +84,23 @@ class StackCoordinatorTest : SysuiTestCase() {
@Test
fun testSetRenderedListOnInteractor() {
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ afterRenderListListener.onAfterRenderList(listOf(entry))
verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
}
@Test
fun testSetNotificationStats_clearableAlerting() {
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ afterRenderListListener.onAfterRenderList(listOf(entry))
verify(activeNotificationsInteractor)
.setNotifStats(
NotifStats(
- 1,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = true,
hasNonClearableSilentNotifs = false,
hasClearableSilentNotifs = false,
)
)
- verifyNoMoreInteractions(stackController)
}
@Test
@@ -113,35 +108,31 @@ class StackCoordinatorTest : SysuiTestCase() {
fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() {
whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ afterRenderListListener.onAfterRenderList(listOf(entry))
verify(activeNotificationsInteractor)
.setNotifStats(
NotifStats(
- 1,
hasNonClearableAlertingNotifs = true,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = false,
hasClearableSilentNotifs = false,
)
)
- verifyNoMoreInteractions(stackController)
}
@Test
fun testSetNotificationStats_clearableSilent() {
whenever(section.bucket).thenReturn(BUCKET_SILENT)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ afterRenderListListener.onAfterRenderList(listOf(entry))
verify(activeNotificationsInteractor)
.setNotifStats(
NotifStats(
- 1,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = false,
hasClearableSilentNotifs = true,
)
)
- verifyNoMoreInteractions(stackController)
}
@Test
@@ -149,35 +140,31 @@ class StackCoordinatorTest : SysuiTestCase() {
fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() {
whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
whenever(section.bucket).thenReturn(BUCKET_SILENT)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ afterRenderListListener.onAfterRenderList(listOf(entry))
verify(activeNotificationsInteractor)
.setNotifStats(
NotifStats(
- 1,
hasNonClearableAlertingNotifs = false,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = true,
hasClearableSilentNotifs = false,
)
)
- verifyNoMoreInteractions(stackController)
}
@Test
fun testSetNotificationStats_nonClearableRedacted() {
entry.setSensitive(true, true)
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ afterRenderListListener.onAfterRenderList(listOf(entry))
verify(activeNotificationsInteractor)
.setNotifStats(
NotifStats(
- 1,
hasNonClearableAlertingNotifs = true,
hasClearableAlertingNotifs = false,
hasNonClearableSilentNotifs = false,
hasClearableSilentNotifs = false,
)
)
- verifyNoMoreInteractions(stackController)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 3a99328fa8ed..30ab416b1cbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -42,6 +42,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -77,6 +78,8 @@ import com.android.systemui.statusbar.phone.ui.DarkIconManager;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.StatusBarOperatorNameViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -1268,6 +1271,15 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mock(StatusBarOperatorNameViewModel.class));
mCollapsedStatusBarViewBinder = new FakeHomeStatusBarViewBinder();
+ HomeStatusBarViewModelFactory homeStatusBarViewModelFactory =
+ new HomeStatusBarViewModelFactory() {
+ @NonNull
+ @Override
+ public HomeStatusBarViewModel create(int displayId) {
+ return mCollapsedStatusBarViewModel;
+ }
+ };
+
return new CollapsedStatusBarFragment(
mStatusBarFragmentComponentFactory,
mOngoingCallController,
@@ -1275,7 +1287,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mShadeExpansionStateManager,
mStatusBarIconController,
mIconManagerFactory,
- mCollapsedStatusBarViewModel,
+ homeStatusBarViewModelFactory,
mCollapsedStatusBarViewBinder,
mStatusBarHideIconsForBouncerManager,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 3de809308702..ee21bdc0b4c2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -24,8 +24,6 @@ import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.util.mockito.mock
@@ -55,7 +53,6 @@ object KeyguardInteractorFactory {
fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor = mock(),
fromOccludedTransitionInteractor: FromOccludedTransitionInteractor = mock(),
fromAlternateBouncerTransitionInteractor: FromAlternateBouncerTransitionInteractor = mock(),
- powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
testScope: CoroutineScope = TestScope(),
): WithDependencies {
// Mock these until they are replaced by kosmos
@@ -73,10 +70,8 @@ object KeyguardInteractorFactory {
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
- powerInteractor = powerInteractor,
KeyguardInteractor(
repository = repository,
- powerInteractor = powerInteractor,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractorImpl(configurationRepository),
shadeRepository = shadeRepository,
@@ -99,7 +94,6 @@ object KeyguardInteractorFactory {
val bouncerRepository: FakeKeyguardBouncerRepository,
val configurationRepository: FakeConfigurationRepository,
val shadeRepository: FakeShadeRepository,
- val powerInteractor: PowerInteractor,
val keyguardInteractor: KeyguardInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index f5f8ef75065f..869bae236d5c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -21,7 +21,6 @@ import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeRepository
@@ -29,7 +28,6 @@ val Kosmos.keyguardInteractor: KeyguardInteractor by
Kosmos.Fixture {
KeyguardInteractor(
repository = keyguardRepository,
- powerInteractor = powerInteractor,
bouncerRepository = keyguardBouncerRepository,
configurationInteractor = configurationInteractor,
shadeRepository = shadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt
index 15d00d9f6994..edc1cce326c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt
@@ -20,4 +20,5 @@ import kotlinx.coroutines.flow.MutableStateFlow
class FakeBouncerTransition : PrimaryBouncerTransition {
override val windowBlurRadius: MutableStateFlow<Float> = MutableStateFlow(0.0f)
+ override val notificationBlurRadius: MutableStateFlow<Float> = MutableStateFlow(0.0f)
}
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 d1619b7959f2..60e092c9709b 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
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.heads
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.window.ui.viewmodel.fakeBouncerTransitions
import kotlinx.coroutines.ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
@@ -99,6 +100,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
+ primaryBouncerTransitions = fakeBouncerTransitions,
aodBurnInViewModel = aodBurnInViewModel,
communalSceneInteractor = communalSceneInteractor,
headsUpNotificationInteractor = { headsUpNotificationInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index b38a723f1fa7..5db0d5a25d83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
+import android.content.testableContext
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -36,6 +37,7 @@ import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStat
var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
Kosmos.Fixture {
HomeStatusBarViewModelImpl(
+ testableContext.displayId,
homeStatusBarInteractor,
homeStatusBarIconBlockListInteractor,
lightsOutInteractor,
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 648990588d29..3a38152825c9 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -420,5 +420,9 @@ message SystemMessage {
// Notify the user that accessibility floating menu is hidden.
// Package: com.android.systemui
NOTE_A11Y_FLOATING_MENU_HIDDEN = 1009;
+
+ // Notify the hearing aid user that input device can be changed to builtin device or hearing device.
+ // Package: android
+ NOTE_HEARING_DEVICE_INPUT_SWITCH = 1012;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 37d045bf6422..8e037c3ba90c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -413,6 +413,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
private final FlashNotificationsController mFlashNotificationsController;
+ private final HearingDevicePhoneCallNotificationController mHearingDeviceNotificationController;
private final UserManagerInternal mUmi;
private AccessibilityUserState getCurrentUserStateLocked() {
@@ -541,7 +542,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
MagnificationController magnificationController,
@Nullable AccessibilityInputFilter inputFilter,
ProxyManager proxyManager,
- PermissionEnforcer permissionEnforcer) {
+ PermissionEnforcer permissionEnforcer,
+ HearingDevicePhoneCallNotificationController hearingDeviceNotificationController) {
super(permissionEnforcer);
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -569,6 +571,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// TODO(b/255426725): not used on tests
mVisibleBgUserIds = null;
mInputManager = context.getSystemService(InputManager.class);
+ if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
+ mHearingDeviceNotificationController = hearingDeviceNotificationController;
+ } else {
+ mHearingDeviceNotificationController = null;
+ }
init();
}
@@ -618,6 +625,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
} else {
mVisibleBgUserIds = null;
}
+ if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
+ mHearingDeviceNotificationController = new HearingDevicePhoneCallNotificationController(
+ context);
+ } else {
+ mHearingDeviceNotificationController = null;
+ }
init();
}
@@ -630,6 +643,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (enableTalkbackAndMagnifierKeyGestures()) {
mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
}
+ if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
+ if (mHearingDeviceNotificationController != null) {
+ mHearingDeviceNotificationController.startListenForCallState();
+ }
+ }
disableAccessibilityMenuToMigrateIfNeeded();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index e3d7062ddb4e..b94fa2f59162 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -22,6 +22,7 @@ import static com.android.server.accessibility.AutoclickIndicatorView.SHOW_INDIC
import android.accessibilityservice.AccessibilityTrace;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -69,7 +70,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
// Lazily created on the first mouse motion event.
private ClickScheduler mClickScheduler;
- private ClickDelayObserver mClickDelayObserver;
+ private AutoclickSettingsObserver mAutoclickSettingsObserver;
private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
private AutoclickIndicatorView mAutoclickIndicatorView;
private WindowManager mWindowManager;
@@ -89,14 +90,17 @@ public class AutoclickController extends BaseEventStreamTransformation {
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (mClickScheduler == null) {
Handler handler = new Handler(mContext.getMainLooper());
- mClickScheduler =
- new ClickScheduler(handler, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
- mClickDelayObserver = new ClickDelayObserver(mUserId, handler);
- mClickDelayObserver.start(mContext.getContentResolver(), mClickScheduler);
-
if (Flags.enableAutoclickIndicator()) {
initiateAutoclickIndicator(handler);
}
+
+ mClickScheduler =
+ new ClickScheduler(handler, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
+ mAutoclickSettingsObserver = new AutoclickSettingsObserver(mUserId, handler);
+ mAutoclickSettingsObserver.start(
+ mContext.getContentResolver(),
+ mClickScheduler,
+ mAutoclickIndicatorScheduler);
}
handleMouseMotion(event, policyFlags);
@@ -156,9 +160,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
@Override
public void onDestroy() {
- if (mClickDelayObserver != null) {
- mClickDelayObserver.stop();
- mClickDelayObserver = null;
+ if (mAutoclickSettingsObserver != null) {
+ mAutoclickSettingsObserver.stop();
+ mAutoclickSettingsObserver = null;
}
if (mClickScheduler != null) {
mClickScheduler.cancel();
@@ -191,19 +195,24 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
/**
- * Observes setting value for autoclick delay, and updates ClickScheduler delay whenever the
- * setting value changes.
+ * Observes autoclick setting values, and updates ClickScheduler delay and indicator size
+ * whenever the setting value changes.
*/
- final private static class ClickDelayObserver extends ContentObserver {
+ final private static class AutoclickSettingsObserver extends ContentObserver {
/** URI used to identify the autoclick delay setting with content resolver. */
private final Uri mAutoclickDelaySettingUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY);
+ /** URI used to identify the autoclick cursor area size setting with content resolver. */
+ private final Uri mAutoclickCursorAreaSizeSettingUri =
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE);
+
private ContentResolver mContentResolver;
private ClickScheduler mClickScheduler;
+ private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
private final int mUserId;
- public ClickDelayObserver(int userId, Handler handler) {
+ public AutoclickSettingsObserver(int userId, Handler handler) {
super(handler);
mUserId = userId;
}
@@ -216,11 +225,13 @@ public class AutoclickController extends BaseEventStreamTransformation {
* changes.
* @param clickScheduler ClickScheduler that should be updated when click delay changes.
* @throws IllegalStateException If internal state is already setup when the method is
- * called.
+ * called.
* @throws NullPointerException If any of the arguments is a null pointer.
*/
- public void start(@NonNull ContentResolver contentResolver,
- @NonNull ClickScheduler clickScheduler) {
+ public void start(
+ @NonNull ContentResolver contentResolver,
+ @NonNull ClickScheduler clickScheduler,
+ @Nullable AutoclickIndicatorScheduler autoclickIndicatorScheduler) {
if (mContentResolver != null || mClickScheduler != null) {
throw new IllegalStateException("Observer already started.");
}
@@ -233,11 +244,20 @@ public class AutoclickController extends BaseEventStreamTransformation {
mContentResolver = contentResolver;
mClickScheduler = clickScheduler;
+ mAutoclickIndicatorScheduler = autoclickIndicatorScheduler;
mContentResolver.registerContentObserver(mAutoclickDelaySettingUri, false, this,
mUserId);
// Initialize mClickScheduler's initial delay value.
onChange(true, mAutoclickDelaySettingUri);
+
+ if (Flags.enableAutoclickIndicator()) {
+ // Register observer to listen to cursor area size setting change.
+ mContentResolver.registerContentObserver(
+ mAutoclickCursorAreaSizeSettingUri, false, this, mUserId);
+ // Initialize mAutoclickIndicatorView's initial size.
+ onChange(true, mAutoclickCursorAreaSizeSettingUri);
+ }
}
/**
@@ -248,7 +268,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
*/
public void stop() {
if (mContentResolver == null || mClickScheduler == null) {
- throw new IllegalStateException("ClickDelayObserver not started.");
+ throw new IllegalStateException("AutoclickSettingsObserver not started.");
}
mContentResolver.unregisterContentObserver(this);
@@ -262,6 +282,18 @@ public class AutoclickController extends BaseEventStreamTransformation {
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT, mUserId);
mClickScheduler.updateDelay(delay);
}
+ if (Flags.enableAutoclickIndicator()
+ && mAutoclickCursorAreaSizeSettingUri.equals(uri)) {
+ int size =
+ Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
+ mUserId);
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.updateCursorAreaSize(size);
+ }
+ }
}
}
@@ -317,6 +349,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
mScheduledShowIndicatorTime = -1;
mHandler.removeCallbacks(this);
}
+
+ public void updateCursorAreaSize(int size) {
+ mAutoclickIndicatorView.setRadius(size);
+ }
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
index 816d8e456a9a..bf5015176f8c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -35,8 +37,7 @@ public class AutoclickIndicatorView extends View {
static final int MINIMAL_ANIMATION_DURATION = 50;
- // TODO(b/383901288): allow users to customize the indicator area.
- static final float RADIUS = 50;
+ private float mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
private final Paint mPaint;
@@ -84,10 +85,10 @@ public class AutoclickIndicatorView extends View {
if (showIndicator) {
mRingRect.set(
- /* left= */ mX - RADIUS,
- /* top= */ mY - RADIUS,
- /* right= */ mX + RADIUS,
- /* bottom= */ mY + RADIUS);
+ /* left= */ mX - mRadius,
+ /* top= */ mY - mRadius,
+ /* right= */ mX + mRadius,
+ /* bottom= */ mY + mRadius);
canvas.drawArc(mRingRect, /* startAngle= */ -90, mSweepAngle, false, mPaint);
}
}
@@ -107,6 +108,10 @@ public class AutoclickIndicatorView extends View {
mY = y;
}
+ public void setRadius(int radius) {
+ mRadius = radius;
+ }
+
public void redrawIndicator() {
showIndicator = true;
invalidate();
diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
new file mode 100644
index 000000000000..d06daf5db127
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaRecorder;
+import android.os.Bundle;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/**
+ * A controller class to handle notification for hearing device during phone calls.
+ */
+public class HearingDevicePhoneCallNotificationController {
+
+ private final TelephonyManager mTelephonyManager;
+ private final TelephonyCallback mTelephonyListener;
+ private final Executor mCallbackExecutor;
+
+ public HearingDevicePhoneCallNotificationController(@NonNull Context context) {
+ mTelephonyListener = new CallStateListener(context);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mCallbackExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ @VisibleForTesting
+ HearingDevicePhoneCallNotificationController(@NonNull Context context,
+ TelephonyCallback telephonyCallback) {
+ mTelephonyListener = telephonyCallback;
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mCallbackExecutor = context.getMainExecutor();
+ }
+
+ /**
+ * Registers a telephony callback to listen for call state changed to handle notification for
+ * hearing device during phone calls.
+ */
+ public void startListenForCallState() {
+ mTelephonyManager.registerTelephonyCallback(mCallbackExecutor, mTelephonyListener);
+ }
+
+ /**
+ * A telephony callback listener to listen to call state changes and show/dismiss notification
+ */
+ @VisibleForTesting
+ static class CallStateListener extends TelephonyCallback implements
+ TelephonyCallback.CallStateListener {
+
+ private static final String TAG =
+ "HearingDevice_CallStateListener";
+ private static final String ACTION_SWITCH_TO_BUILTIN_MIC =
+ "com.android.server.accessibility.hearingdevice.action.SWITCH_TO_BUILTIN_MIC";
+ private static final String ACTION_SWITCH_TO_HEARING_MIC =
+ "com.android.server.accessibility.hearingdevice.action.SWITCH_TO_HEARING_MIC";
+ private static final String ACTION_BLUETOOTH_DEVICE_DETAILS =
+ "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS";
+ private static final String KEY_BLUETOOTH_ADDRESS = "device_address";
+ private static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
+ private static final int MICROPHONE_SOURCE_VOICE_COMMUNICATION =
+ MediaRecorder.AudioSource.VOICE_COMMUNICATION;
+ private static final AudioDeviceAttributes BUILTIN_MIC = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_BUILTIN_MIC, "");
+
+ private final Context mContext;
+ private NotificationManager mNotificationManager;
+ private AudioManager mAudioManager;
+ private BroadcastReceiver mHearingDeviceActionReceiver;
+ private BluetoothDevice mHearingDevice;
+ private boolean mIsNotificationShown = false;
+
+ CallStateListener(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public void onCallStateChanged(int state) {
+ // NotificationManagerService and AudioService are all initialized after
+ // AccessibilityManagerService.
+ // Can not get them in constructor. Need to get these services until callback is
+ // triggered.
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+ mAudioManager = mContext.getSystemService(AudioManager.class);
+ if (mNotificationManager == null || mAudioManager == null) {
+ Log.w(TAG, "NotificationManager or AudioManager is not prepare yet.");
+ return;
+ }
+
+ if (state == TelephonyManager.CALL_STATE_IDLE) {
+ dismissNotificationIfNeeded();
+
+ if (mHearingDevice != null) {
+ // reset to its original status
+ setMicrophonePreferredForCalls(mHearingDevice.isMicrophonePreferredForCalls());
+ }
+ mHearingDevice = null;
+ }
+ if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
+ mHearingDevice = getSupportedInputHearingDeviceInfo(
+ mAudioManager.getAvailableCommunicationDevices());
+ if (mHearingDevice != null) {
+ showNotificationIfNeeded();
+ }
+ }
+ }
+
+ private void showNotificationIfNeeded() {
+ if (mIsNotificationShown) {
+ return;
+ }
+
+ showNotification(mHearingDevice.isMicrophonePreferredForCalls());
+ mIsNotificationShown = true;
+ }
+
+ private void dismissNotificationIfNeeded() {
+ if (!mIsNotificationShown) {
+ return;
+ }
+
+ dismissNotification();
+ mIsNotificationShown = false;
+ }
+
+ private void showNotification(boolean useRemoteMicrophone) {
+ mNotificationManager.notify(
+ SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH,
+ createSwitchInputNotification(useRemoteMicrophone));
+ registerReceiverIfNeeded();
+ }
+
+ private void dismissNotification() {
+ unregisterReceiverIfNeeded();
+ mNotificationManager.cancel(
+ SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH);
+ }
+
+ private BluetoothDevice getSupportedInputHearingDeviceInfo(List<AudioDeviceInfo> infoList) {
+ final BluetoothAdapter bluetoothAdapter = mContext.getSystemService(
+ BluetoothManager.class).getAdapter();
+ if (bluetoothAdapter == null) {
+ return null;
+ }
+ if (!isHapClientSupported()) {
+ return null;
+ }
+
+ final Set<String> inputDeviceAddress = Arrays.stream(
+ mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).map(
+ AudioDeviceInfo::getAddress).collect(Collectors.toSet());
+
+ //TODO: b/370812132 - Need to update if TYPE_LEA_HEARING_AID is added
+ final AudioDeviceInfo hearingDeviceInfo = infoList.stream()
+ .filter(info -> info.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET)
+ .filter(info -> inputDeviceAddress.contains(info.getAddress()))
+ .filter(info -> isHapClientDevice(bluetoothAdapter, info))
+ .findAny()
+ .orElse(null);
+
+ return (hearingDeviceInfo != null) ? bluetoothAdapter.getRemoteDevice(
+ hearingDeviceInfo.getAddress()) : null;
+ }
+
+ @VisibleForTesting
+ boolean isHapClientDevice(BluetoothAdapter bluetoothAdapter, AudioDeviceInfo info) {
+ BluetoothDevice device = bluetoothAdapter.getRemoteDevice(info.getAddress());
+ return ArrayUtils.contains(device.getUuids(), BluetoothUuid.HAS);
+ }
+
+ @VisibleForTesting
+ boolean isHapClientSupported() {
+ return BluetoothAdapter.getDefaultAdapter().getSupportedProfiles().contains(
+ BluetoothProfile.HAP_CLIENT);
+ }
+
+ private Notification createSwitchInputNotification(boolean useRemoteMicrophone) {
+ return new Notification.Builder(mContext,
+ SystemNotificationChannels.ACCESSIBILITY_HEARING_DEVICE)
+ .setContentTitle(getSwitchInputTitle(useRemoteMicrophone))
+ .setContentText(getSwitchInputMessage(useRemoteMicrophone))
+ .setSmallIcon(R.drawable.ic_settings_24dp)
+ .setColor(mContext.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setLocalOnly(true)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setContentIntent(createPendingIntent(ACTION_BLUETOOTH_DEVICE_DETAILS))
+ .setActions(buildSwitchInputAction(useRemoteMicrophone),
+ buildOpenSettingsAction())
+ .build();
+ }
+
+ private Notification.Action buildSwitchInputAction(boolean useRemoteMicrophone) {
+ return useRemoteMicrophone
+ ? new Notification.Action.Builder(null,
+ mContext.getString(R.string.hearing_device_notification_switch_button),
+ createPendingIntent(ACTION_SWITCH_TO_BUILTIN_MIC)).build()
+ : new Notification.Action.Builder(null,
+ mContext.getString(R.string.hearing_device_notification_switch_button),
+ createPendingIntent(ACTION_SWITCH_TO_HEARING_MIC)).build();
+ }
+
+ private Notification.Action buildOpenSettingsAction() {
+ return new Notification.Action.Builder(null,
+ mContext.getString(R.string.hearing_device_notification_settings_button),
+ createPendingIntent(ACTION_BLUETOOTH_DEVICE_DETAILS)).build();
+ }
+
+ private PendingIntent createPendingIntent(String action) {
+ final Intent intent = new Intent(action);
+
+ switch (action) {
+ case ACTION_SWITCH_TO_BUILTIN_MIC, ACTION_SWITCH_TO_HEARING_MIC -> {
+ intent.setPackage(mContext.getPackageName());
+ return PendingIntent.getBroadcast(mContext, /* requestCode = */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ }
+ case ACTION_BLUETOOTH_DEVICE_DETAILS -> {
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_BLUETOOTH_ADDRESS, mHearingDevice.getAddress());
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return PendingIntent.getActivity(mContext, /* requestCode = */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ }
+ }
+ return null;
+ }
+
+ private void setMicrophonePreferredForCalls(boolean useRemoteMicrophone) {
+ if (useRemoteMicrophone) {
+ switchToHearingMic();
+ } else {
+ switchToBuiltinMic();
+ }
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private void switchToBuiltinMic() {
+ mAudioManager.clearPreferredDevicesForCapturePreset(
+ MICROPHONE_SOURCE_VOICE_COMMUNICATION);
+ mAudioManager.setPreferredDeviceForCapturePreset(MICROPHONE_SOURCE_VOICE_COMMUNICATION,
+ BUILTIN_MIC);
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private void switchToHearingMic() {
+ // clear config to let audio manager to determine next priority device. We can assume
+ // user connects to hearing device here, so next priority device should be hearing
+ // device.
+ mAudioManager.clearPreferredDevicesForCapturePreset(
+ MICROPHONE_SOURCE_VOICE_COMMUNICATION);
+ }
+
+ private void registerReceiverIfNeeded() {
+ if (mHearingDeviceActionReceiver != null) {
+ return;
+ }
+ mHearingDeviceActionReceiver = new HearingDeviceActionReceiver();
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_SWITCH_TO_BUILTIN_MIC);
+ intentFilter.addAction(ACTION_SWITCH_TO_HEARING_MIC);
+ mContext.registerReceiver(mHearingDeviceActionReceiver, intentFilter,
+ Manifest.permission.MANAGE_ACCESSIBILITY, null, Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ private void unregisterReceiverIfNeeded() {
+ if (mHearingDeviceActionReceiver == null) {
+ return;
+ }
+ mContext.unregisterReceiver(mHearingDeviceActionReceiver);
+ mHearingDeviceActionReceiver = null;
+ }
+
+ private CharSequence getSwitchInputTitle(boolean useRemoteMicrophone) {
+ return useRemoteMicrophone
+ ? mContext.getString(
+ R.string.hearing_device_switch_phone_mic_notification_title)
+ : mContext.getString(
+ R.string.hearing_device_switch_hearing_mic_notification_title);
+ }
+
+ private CharSequence getSwitchInputMessage(boolean useRemoteMicrophone) {
+ return useRemoteMicrophone
+ ? mContext.getString(
+ R.string.hearing_device_switch_phone_mic_notification_text)
+ : mContext.getString(
+ R.string.hearing_device_switch_hearing_mic_notification_text);
+ }
+
+ private class HearingDeviceActionReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TextUtils.isEmpty(action)) {
+ return;
+ }
+
+ if (ACTION_SWITCH_TO_BUILTIN_MIC.equals(action)) {
+ switchToBuiltinMic();
+ showNotification(/* useRemoteMicrophone= */ false);
+ } else if (ACTION_SWITCH_TO_HEARING_MIC.equals(action)) {
+ switchToHearingMic();
+ showNotification(/* useRemoteMicrophone= */ true);
+ }
+ }
+ }
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 57d33f1a051e..43764442e2cf 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -50,7 +50,10 @@ import android.app.appsearch.observer.ObserverSpec;
import android.app.appsearch.observer.SchemaChangeInfo;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.SigningInfo;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -292,7 +295,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
safeExecuteAppFunctionCallback,
/* bindFlags= */ Context.BIND_AUTO_CREATE
| Context.BIND_FOREGROUND_SERVICE,
- callerBinder);
+ callerBinder,
+ callingUid);
})
.exceptionally(
ex -> {
@@ -444,7 +448,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@NonNull ICancellationSignal cancellationSignalTransport,
@NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
int bindFlags,
- @NonNull IBinder callerBinder) {
+ @NonNull IBinder callerBinder,
+ int callingUid) {
CancellationSignal cancellationSignal =
CancellationSignal.fromTransport(cancellationSignalTransport);
ICancellationCallback cancellationCallback =
@@ -465,7 +470,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
new RunAppFunctionServiceCallback(
requestInternal,
cancellationCallback,
- safeExecuteAppFunctionCallback),
+ safeExecuteAppFunctionCallback,
+ getPackageSigningInfo(
+ targetUser,
+ requestInternal.getCallingPackage(),
+ callingUid)),
callerBinder);
if (!bindServiceResult) {
@@ -477,6 +486,23 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
}
+ @NonNull
+ private SigningInfo getPackageSigningInfo(
+ @NonNull UserHandle targetUser, @NonNull String packageName, int uid) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(targetUser);
+
+ PackageInfo packageInfo;
+ packageInfo =
+ Objects.requireNonNull(
+ mPackageManagerInternal.getPackageInfo(
+ packageName,
+ PackageManager.GET_SIGNING_CERTIFICATES,
+ uid,
+ targetUser.getIdentifier()));
+ return Objects.requireNonNull(packageInfo.signingInfo);
+ }
+
private AppSearchManager getAppSearchManagerAsUser(@NonNull UserHandle userHandle) {
return mContext.createContextAsUser(userHandle, /* flags= */ 0)
.getSystemService(AppSearchManager.class);
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
index 4cba8ecb2092..0cec09dcde8b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -23,6 +23,7 @@ import android.app.appfunctions.IAppFunctionService;
import android.app.appfunctions.ICancellationCallback;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.content.pm.SigningInfo;
import android.os.SystemClock;
import android.util.Slog;
@@ -38,14 +39,17 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
private final ExecuteAppFunctionAidlRequest mRequestInternal;
private final SafeOneTimeExecuteAppFunctionCallback mSafeExecuteAppFunctionCallback;
private final ICancellationCallback mCancellationCallback;
+ private final SigningInfo mCallerSigningInfo;
public RunAppFunctionServiceCallback(
ExecuteAppFunctionAidlRequest requestInternal,
ICancellationCallback cancellationCallback,
- SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
- this.mRequestInternal = requestInternal;
- this.mSafeExecuteAppFunctionCallback = safeExecuteAppFunctionCallback;
- this.mCancellationCallback = cancellationCallback;
+ SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
+ SigningInfo callerSigningInfo) {
+ mRequestInternal = requestInternal;
+ mSafeExecuteAppFunctionCallback = safeExecuteAppFunctionCallback;
+ mCancellationCallback = cancellationCallback;
+ mCallerSigningInfo = callerSigningInfo;
}
@Override
@@ -58,6 +62,7 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
service.executeAppFunction(
mRequestInternal.getClientRequest(),
mRequestInternal.getCallingPackage(),
+ mCallerSigningInfo,
mCancellationCallback,
new IExecuteAppFunctionCallback.Stub() {
@Override
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index d3e808fbd3d1..7456c5099698 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -265,8 +265,8 @@ class InputController {
mInputManagerInternal.setPointerIconVisible(visible, displayId);
}
- void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
- mInputManagerInternal.setMousePointerAccelerationEnabled(enabled, displayId);
+ void setMouseScalingEnabled(boolean enabled, int displayId) {
+ mInputManagerInternal.setMouseScalingEnabled(enabled, displayId);
}
void setDisplayEligibilityForPointerCapture(boolean isEligible, int displayId) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 6bf60bf1ddf1..260ea75a1f4c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1518,7 +1518,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
final long token = Binder.clearCallingIdentity();
try {
- mInputController.setMousePointerAccelerationEnabled(false, displayId);
+ mInputController.setMouseScalingEnabled(false, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
if (isTrustedDisplay) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b536dc524a80..5a198a106fc5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19374,7 +19374,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (preventIntentRedirectCollectNestedKeysOnServerIfNotCollected()) {
// this flag will be ramped to public.
- intent.collectExtraIntentKeys();
+ intent.collectExtraIntentKeys(true);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bd2714211796..3f6484f0f58e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8571,6 +8571,12 @@ public class AudioService extends IAudioService.Stub
return true;
}
+ private boolean shouldPreserveVolume(boolean userSwitch, VolumeGroupState vgs) {
+ // as for STREAM_MUSIC, preserve volume from one user to the next except
+ // Android Automotive platform
+ return (userSwitch && vgs.isMusic()) && !isPlatformAutomotive();
+ }
+
private void readVolumeGroupsSettings(boolean userSwitch) {
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
@@ -8579,8 +8585,7 @@ public class AudioService extends IAudioService.Stub
}
for (int i = 0; i < sVolumeGroupStates.size(); i++) {
VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
- // as for STREAM_MUSIC, preserve volume from one user to the next.
- if (!(userSwitch && vgs.isMusic())) {
+ if (!shouldPreserveVolume(userSwitch, vgs)) {
vgs.clearIndexCache();
vgs.readSettings();
}
@@ -9019,6 +9024,11 @@ public class AudioService extends IAudioService.Stub
mIndexMap.clear();
}
+ private @UserIdInt int getVolumePersistenceUserId() {
+ return isMusic() && !isPlatformAutomotive()
+ ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT;
+ }
+
private void persistVolumeGroup(int device) {
// No need to persist the index if the volume group is backed up
// by a public stream type as this is redundant
@@ -9036,7 +9046,7 @@ public class AudioService extends IAudioService.Stub
boolean success = mSettings.putSystemIntForUser(mContentResolver,
getSettingNameForDevice(device),
getIndex(device),
- isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
+ getVolumePersistenceUserId());
if (!success) {
Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name());
}
@@ -9059,7 +9069,7 @@ public class AudioService extends IAudioService.Stub
String name = getSettingNameForDevice(device);
index = mSettings.getSystemIntForUser(
mContentResolver, name, defaultIndex,
- isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
+ getVolumePersistenceUserId());
if (index == -1) {
continue;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c8192e534f5c..b530da2a5f5e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2246,10 +2246,6 @@ public final class DisplayManagerService extends SystemService {
@GuardedBy("mSyncRoot")
private void handleLogicalDisplayDisconnectedLocked(LogicalDisplay display) {
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- Slog.e(TAG, "DisplayDisconnected shouldn't be received when the flag is off");
- return;
- }
releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED);
mExternalDisplayPolicy.handleLogicalDisplayDisconnectedLocked(display);
}
@@ -2315,11 +2311,6 @@ public final class DisplayManagerService extends SystemService {
@SuppressLint("AndroidFrameworkRequiresPermission")
private void handleLogicalDisplayConnectedLocked(LogicalDisplay display) {
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- Slog.e(TAG, "DisplayConnected shouldn't be received when the flag is off");
- return;
- }
-
setupLogicalDisplay(display);
if (ExternalDisplayPolicy.isExternalDisplayLocked(display)) {
@@ -2346,9 +2337,6 @@ public final class DisplayManagerService extends SystemService {
private void handleLogicalDisplayAddedLocked(LogicalDisplay display) {
final int displayId = display.getDisplayIdLocked();
final boolean isDefault = displayId == Display.DEFAULT_DISPLAY;
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- setupLogicalDisplay(display);
- }
// Wake up waitForDefaultDisplay.
if (isDefault) {
@@ -2443,21 +2431,17 @@ public final class DisplayManagerService extends SystemService {
}
private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) {
- // With display management, the display is removed when disabled, and it might still exist.
+ // The display is removed when disabled, and it might still exist.
// Resources must only be released when the disconnected signal is received.
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- if (display.isValidLocked()) {
- updateViewportPowerStateLocked(display);
- }
+ if (display.isValidLocked()) {
+ updateViewportPowerStateLocked(display);
+ }
- // Note: This method is only called if the display was enabled before being removed.
- sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+ // Note: This method is only called if the display was enabled before being removed.
+ sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
- if (display.isValidLocked()) {
- applyDisplayChangedLocked(display);
- }
- } else {
- releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+ if (display.isValidLocked()) {
+ applyDisplayChangedLocked(display);
}
if (mDisplayTopologyCoordinator != null) {
mDisplayTopologyCoordinator.onDisplayRemoved(display.getDisplayIdLocked());
@@ -4565,13 +4549,11 @@ public final class DisplayManagerService extends SystemService {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- if ((internalEventFlagsMask
- & DisplayManagerGlobal
- .INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
- mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS,
- "Permission required to get signals about connection events.");
- }
+ if ((internalEventFlagsMask
+ & DisplayManagerGlobal
+ .INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS,
+ "Permission required to get signals about connection events.");
}
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index e46397bc8ab7..f6b2591ea440 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -179,12 +179,10 @@ class DisplayManagerShellCommand extends ShellCommand {
pw.println(" Sets brightness to docked + idle screen brightness mode");
pw.println(" undock");
pw.println(" Sets brightness to active (normal) screen brightness mode");
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- pw.println(" enable-display DISPLAY_ID");
- pw.println(" Enable the DISPLAY_ID. Only possible if this is a connected display.");
- pw.println(" disable-display DISPLAY_ID");
- pw.println(" Disable the DISPLAY_ID. Only possible if this is a connected display.");
- }
+ pw.println(" enable-display DISPLAY_ID");
+ pw.println(" Enable the DISPLAY_ID. Only possible if this is a connected display.");
+ pw.println(" disable-display DISPLAY_ID");
+ pw.println(" Disable the DISPLAY_ID. Only possible if this is a connected display.");
pw.println(" power-reset DISPLAY_ID");
pw.println(" Turn the DISPLAY_ID power to a state the display supposed to have.");
pw.println(" power-off DISPLAY_ID");
@@ -601,11 +599,6 @@ class DisplayManagerShellCommand extends ShellCommand {
}
private int setDisplayEnabled(boolean enable) {
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- getErrPrintWriter()
- .println("Error: external display management is not available on this device.");
- return 1;
- }
final String displayIdText = getNextArg();
if (displayIdText == null) {
getErrPrintWriter().println("Error: no displayId specified");
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 519763a1c3db..a47853c8e555 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -142,14 +142,6 @@ class ExternalDisplayPolicy {
mDisplayIdsWaitingForBootCompletion.clear();
}
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- if (DEBUG) {
- Slog.d(TAG, "External display management is not enabled on your device:"
- + " cannot register thermal listener.");
- }
- return;
- }
-
if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
if (DEBUG) {
Slog.d(TAG, "ConnectedDisplayErrorHandlingEnabled is not enabled on your device:"
@@ -173,14 +165,6 @@ class ExternalDisplayPolicy {
return;
}
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- if (DEBUG) {
- Slog.d(TAG, "setExternalDisplayEnabledLocked: External display management is not"
- + " enabled on your device, cannot enable/disable display.");
- }
- return;
- }
-
if (enabled && !isExternalDisplayAllowed()) {
Slog.w(TAG, "setExternalDisplayEnabledLocked: External display can not be enabled"
+ " because it is currently not allowed.");
@@ -202,14 +186,6 @@ class ExternalDisplayPolicy {
return;
}
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- if (DEBUG) {
- Slog.d(TAG, "handleExternalDisplayConnectedLocked connected display management"
- + " flag is off");
- }
- return;
- }
-
if (!mIsBootCompleted) {
mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
return;
@@ -251,10 +227,6 @@ class ExternalDisplayPolicy {
void handleLogicalDisplayDisconnectedLocked(@NonNull final LogicalDisplay logicalDisplay) {
// Type of the display here is always UNKNOWN, so we can't verify it is an external display
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- return;
- }
-
var displayId = logicalDisplay.getDisplayIdLocked();
if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
return;
@@ -271,10 +243,6 @@ class ExternalDisplayPolicy {
return;
}
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- return;
- }
-
mExternalDisplayStatsService.onDisplayAdded(logicalDisplay.getDisplayIdLocked());
}
@@ -289,10 +257,6 @@ class ExternalDisplayPolicy {
}
}
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- return;
- }
-
if (isShown) {
mExternalDisplayStatsService.onPresentationWindowAdded(displayId);
} else {
@@ -306,12 +270,6 @@ class ExternalDisplayPolicy {
return;
}
- if (!mFlags.isConnectedDisplayManagementEnabled()) {
- Slog.e(TAG, "disableExternalDisplayLocked shouldn't be called when the"
- + " connected display management flag is off");
- return;
- }
-
if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
if (DEBUG) {
Slog.d(TAG, "disableExternalDisplayLocked shouldn't be called when the"
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 006921572977..ecc8896b69c6 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -823,18 +823,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (wasPreviouslyUpdated) {
// The display isn't actually removed from our internal data structures until
// after the notification is sent; see {@link #sendUpdatesForDisplaysLocked}.
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- if (mDisplaysEnabledCache.get(displayId)) {
- // We still need to send LOGICAL_DISPLAY_EVENT_DISCONNECTED
- reloop = true;
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
- } else {
- mUpdatedLogicalDisplays.delete(displayId);
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DISCONNECTED;
- }
+ if (mDisplaysEnabledCache.get(displayId)) {
+ // We still need to send LOGICAL_DISPLAY_EVENT_DISCONNECTED
+ reloop = true;
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
} else {
mUpdatedLogicalDisplays.delete(displayId);
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DISCONNECTED;
}
} else {
// This display never left this class, safe to remove without notification
@@ -845,20 +840,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// The display is new.
} else if (!wasPreviouslyUpdated) {
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- // We still need to send LOGICAL_DISPLAY_EVENT_ADDED
- reloop = true;
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CONNECTED;
- } else {
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_ADDED;
- }
+ // We still need to send LOGICAL_DISPLAY_EVENT_ADDED
+ reloop = true;
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CONNECTED;
// Underlying displays device has changed to a different one.
} else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_SWAPPED;
// Something about the display device has changed.
- } else if (mFlags.isConnectedDisplayManagementEnabled()
- && wasPreviouslyEnabled != isCurrentlyEnabled) {
+ } else if (wasPreviouslyEnabled != isCurrentlyEnabled) {
int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED :
LOGICAL_DISPLAY_EVENT_REMOVED;
logicalDisplayEventMask |= event;
@@ -936,17 +926,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED);
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
- }
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_BASIC_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CONNECTED);
- }
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CONNECTED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED);
sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);
@@ -996,23 +982,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
+ "display=" + id + " with device=" + uniqueId);
}
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_ADDED) {
- mDisplaysEnabledCache.put(id, true);
- } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
- mDisplaysEnabledCache.delete(id);
- }
+ if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_ADDED) {
+ mDisplaysEnabledCache.put(id, true);
+ } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
+ mDisplaysEnabledCache.delete(id);
}
mListener.onLogicalDisplayEventLocked(display, logicalDisplayEvent);
- if (mFlags.isConnectedDisplayManagementEnabled()) {
- if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
- mLogicalDisplays.delete(id);
- }
- } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
- // We wait until we sent the EVENT_REMOVED event before actually removing the
- // display.
+ if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
mLogicalDisplays.delete(id);
}
}
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 addfbf1833b9..4e57d6791ff6 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -42,10 +42,6 @@ public class DisplayManagerFlags {
Flags.FLAG_ENABLE_PORT_IN_DISPLAY_LAYOUT,
Flags::enablePortInDisplayLayout);
- private final FlagState mConnectedDisplayManagementFlagState = new FlagState(
- Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
- Flags::enableConnectedDisplayManagement);
-
private final FlagState mAdaptiveToneImprovements1 = new FlagState(
Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1,
Flags::enableAdaptiveToneImprovements1);
@@ -269,11 +265,6 @@ public class DisplayManagerFlags {
return mPortInDisplayLayoutFlagState.isEnabled();
}
- /** Returns whether connected display management is enabled or not. */
- public boolean isConnectedDisplayManagementEnabled() {
- return mConnectedDisplayManagementFlagState.isEnabled();
- }
-
/** Returns whether power throttling clamper is enabled on not. */
public boolean isPowerThrottlingClamperEnabled() {
return mPowerThrottlingClamperFlagState.isEnabled();
@@ -572,7 +563,6 @@ public class DisplayManagerFlags {
pw.println(" " + mAdaptiveToneImprovements2);
pw.println(" " + mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState);
pw.println(" " + mConnectedDisplayErrorHandlingFlagState);
- pw.println(" " + mConnectedDisplayManagementFlagState);
pw.println(" " + mDisplayOffloadFlagState);
pw.println(" " + mExternalDisplayLimitModeState);
pw.println(" " + mDisplayTopology);
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 eccbbb14c4ea..afae07c88f8d 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
@@ -29,14 +29,6 @@ flag {
}
flag {
- name: "enable_connected_display_management"
- namespace: "display_manager"
- description: "Feature flag for Connected Display management"
- bug: "280739508"
- is_fixed_read_only: true
-}
-
-flag {
name: "enable_power_throttling_clamper"
namespace: "display_manager"
description: "Feature flag for Power Throttling Clamper"
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index eea5c982c537..4505d0e2d799 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -78,3 +78,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "datetime_notifications"
+ # "location" is used by the Android System Time team for feature flags.
+ namespace: "location"
+ description: "Enable the time notifications feature, a toggle to enable/disable time-related notifications in Date & Time Settings"
+ bug: "283267917"
+}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 9f785ac81398..24296406da00 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -19,6 +19,7 @@ package com.android.server.input;
import static android.hardware.input.InputGestureData.createKeyTrigger;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
@@ -240,6 +241,13 @@ final class InputGestureManager {
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK));
}
+ if (enableVoiceAccessKeyGestures()) {
+ systemShortcuts.add(
+ createKeyGesture(
+ KeyEvent.KEYCODE_V,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ }
if (enableTaskResizingKeyboardShortcuts()) {
systemShortcuts.add(createKeyGesture(
KeyEvent.KEYCODE_LEFT_BRACKET,
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index bc44fed21f2d..4e5c720f9f1c 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -104,13 +104,16 @@ public abstract class InputManagerInternal {
public abstract PointF getCursorPosition(int displayId);
/**
- * Enables or disables pointer acceleration for mouse movements.
+ * Set whether all pointer scaling, including linear scaling based on the
+ * user's pointer speed setting, should be enabled or disabled for mice.
*
* Note that this only affects pointer movements from mice (that is, pointing devices which send
* relative motions, including trackballs and pointing sticks), not from other pointer devices
* such as touchpads and styluses.
+ *
+ * Scaling is enabled by default on new displays until it is explicitly disabled.
*/
- public abstract void setMousePointerAccelerationEnabled(boolean enabled, int displayId);
+ public abstract void setMouseScalingEnabled(boolean enabled, int displayId);
/**
* Sets the eligibility of windows on a given display for pointer capture. If a display is
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 559b4ae64e50..b2c35e1f362e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1382,9 +1382,9 @@ public class InputManagerService extends IInputManager.Stub
mNative.setPointerSpeed(speed);
}
- private void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
+ private void setMouseScalingEnabled(boolean enabled, int displayId) {
updateAdditionalDisplayInputProperties(displayId,
- properties -> properties.mousePointerAccelerationEnabled = enabled);
+ properties -> properties.mouseScalingEnabled = enabled);
}
private void setPointerIconVisible(boolean visible, int displayId) {
@@ -2232,8 +2232,8 @@ public class InputManagerService extends IInputManager.Stub
pw.println("displayId: " + mAdditionalDisplayInputProperties.keyAt(i));
final AdditionalDisplayInputProperties properties =
mAdditionalDisplayInputProperties.valueAt(i);
- pw.println("mousePointerAccelerationEnabled: "
- + properties.mousePointerAccelerationEnabled);
+ pw.println("mouseScalingEnabled: "
+ + properties.mouseScalingEnabled);
pw.println("pointerIconVisible: " + properties.pointerIconVisible);
}
} finally {
@@ -3575,8 +3575,8 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
- InputManagerService.this.setMousePointerAccelerationEnabled(enabled, displayId);
+ public void setMouseScalingEnabled(boolean enabled, int displayId) {
+ InputManagerService.this.setMouseScalingEnabled(enabled, displayId);
}
@Override
@@ -3716,15 +3716,15 @@ public class InputManagerService extends IInputManager.Stub
private static class AdditionalDisplayInputProperties {
static final boolean DEFAULT_POINTER_ICON_VISIBLE = true;
- static final boolean DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED = true;
+ static final boolean DEFAULT_MOUSE_SCALING_ENABLED = true;
/**
- * Whether to enable mouse pointer acceleration on this display. Note that this only affects
+ * Whether to enable mouse pointer scaling on this display. Note that this only affects
* pointer movements from mice (that is, pointing devices which send relative motions,
* including trackballs and pointing sticks), not from other pointer devices such as
* touchpads and styluses.
*/
- public boolean mousePointerAccelerationEnabled;
+ public boolean mouseScalingEnabled;
// Whether the pointer icon should be visible or hidden on this display.
public boolean pointerIconVisible;
@@ -3734,12 +3734,12 @@ public class InputManagerService extends IInputManager.Stub
}
public boolean allDefaults() {
- return mousePointerAccelerationEnabled == DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED
+ return mouseScalingEnabled == DEFAULT_MOUSE_SCALING_ENABLED
&& pointerIconVisible == DEFAULT_POINTER_ICON_VISIBLE;
}
public void reset() {
- mousePointerAccelerationEnabled = DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED;
+ mouseScalingEnabled = DEFAULT_MOUSE_SCALING_ENABLED;
pointerIconVisible = DEFAULT_POINTER_ICON_VISIBLE;
}
}
@@ -3754,14 +3754,14 @@ public class InputManagerService extends IInputManager.Stub
mAdditionalDisplayInputProperties.put(displayId, properties);
}
final boolean oldPointerIconVisible = properties.pointerIconVisible;
- final boolean oldMouseAccelerationEnabled = properties.mousePointerAccelerationEnabled;
+ final boolean oldMouseScalingEnabled = properties.mouseScalingEnabled;
updater.accept(properties);
if (oldPointerIconVisible != properties.pointerIconVisible) {
mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible);
}
- if (oldMouseAccelerationEnabled != properties.mousePointerAccelerationEnabled) {
- mNative.setMousePointerAccelerationEnabled(displayId,
- properties.mousePointerAccelerationEnabled);
+ if (oldMouseScalingEnabled != properties.mouseScalingEnabled) {
+ mNative.setMouseScalingEnabled(displayId,
+ properties.mouseScalingEnabled);
}
if (properties.allDefaults()) {
mAdditionalDisplayInputProperties.remove(displayId);
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index d426e8292f14..4d38c8401e2d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -130,7 +130,7 @@ interface NativeInputManagerService {
void setPointerSpeed(int speed);
- void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
+ void setMouseScalingEnabled(int displayId, boolean enabled);
void setMouseReverseVerticalScrollingEnabled(boolean enabled);
@@ -421,7 +421,7 @@ interface NativeInputManagerService {
public native void setPointerSpeed(int speed);
@Override
- public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
+ public native void setMouseScalingEnabled(int displayId, boolean enabled);
@Override
public native void setMouseReverseVerticalScrollingEnabled(boolean enabled);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 87d809b5e850..f5ed4d586131 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -32,7 +32,10 @@ import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubTransactionCallback;
import android.os.Binder;
import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
+import android.os.WorkSource;
import android.util.Log;
import android.util.SparseArray;
@@ -54,6 +57,16 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/** Message used by noteOp when this client receives a message from an endpoint. */
private static final String RECEIVE_MSG_NOTE = "ContextHubEndpointMessageDelivery";
+ /** The duration of wakelocks acquired during HAL callbacks */
+ private static final long WAKELOCK_TIMEOUT_MILLIS = 5 * 1000;
+
+ /*
+ * Internal interface used to invoke client callbacks.
+ */
+ interface CallbackConsumer {
+ void accept(IContextHubEndpointCallback callback) throws RemoteException;
+ }
+
/** The context of the service. */
private final Context mContext;
@@ -134,6 +147,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
private final int mUid;
+ /** Wakelock held while nanoapp message are in flight to the client */
+ private final WakeLock mWakeLock;
+
/* package */ ContextHubEndpointBroker(
Context context,
IEndpointCommunication hubInterface,
@@ -158,6 +174,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackageName, this);
+
+ PowerManager powerManager = context.getSystemService(PowerManager.class);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.setWorkSource(new WorkSource(mUid, mPackageName));
+ mWakeLock.setReferenceCounted(true);
}
@Override
@@ -302,6 +323,13 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
}
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ public void onCallbackFinished() {
+ super.onCallbackFinished_enforcePermission();
+ releaseWakeLock();
+ }
+
/** Invoked when the underlying binder of this broker has died at the client process. */
@Override
public void binderDied() {
@@ -357,15 +385,13 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true));
}
- if (mContextHubEndpointCallback != null) {
- try {
- mContextHubEndpointCallback.onSessionOpenRequest(
- sessionId, initiator, serviceDescriptor);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException while calling onSessionOpenRequest", e);
- cleanupSessionResources(sessionId);
- return;
- }
+ boolean success =
+ invokeCallback(
+ (consumer) ->
+ consumer.onSessionOpenRequest(
+ sessionId, initiator, serviceDescriptor));
+ if (!success) {
+ cleanupSessionResources(sessionId);
}
}
@@ -374,14 +400,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
Log.w(TAG, "Unknown session ID in onCloseEndpointSession: id=" + sessionId);
return;
}
- if (mContextHubEndpointCallback != null) {
- try {
- mContextHubEndpointCallback.onSessionClosed(
- sessionId, ContextHubServiceUtil.toAppHubEndpointReason(reason));
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException while calling onSessionClosed", e);
- }
- }
+
+ invokeCallback(
+ (consumer) ->
+ consumer.onSessionClosed(
+ sessionId, ContextHubServiceUtil.toAppHubEndpointReason(reason)));
}
/* package */ void onEndpointSessionOpenComplete(int sessionId) {
@@ -392,13 +415,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
mSessionInfoMap.get(sessionId).setSessionState(SessionInfo.SessionState.ACTIVE);
}
- if (mContextHubEndpointCallback != null) {
- try {
- mContextHubEndpointCallback.onSessionOpenComplete(sessionId);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException while calling onSessionClosed", e);
- }
- }
+
+ invokeCallback((consumer) -> consumer.onSessionOpenComplete(sessionId));
}
/* package */ void onMessageReceived(int sessionId, HubMessage message) {
@@ -440,14 +458,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
return;
}
- if (mContextHubEndpointCallback != null) {
- try {
- mContextHubEndpointCallback.onMessageReceived(sessionId, message);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException while calling onMessageReceived", e);
- sendMessageDeliveryStatus(
- sessionId, message.getMessageSequenceNumber(), ErrorCode.TRANSIENT_ERROR);
- }
+ boolean success =
+ invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message));
+ if (!success) {
+ sendMessageDeliveryStatus(
+ sessionId, message.getMessageSequenceNumber(), ErrorCode.TRANSIENT_ERROR);
}
}
@@ -520,4 +535,46 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
Collection<String> requiredPermissions = targetEndpointInfo.getRequiredPermissions();
return ContextHubServiceUtil.hasPermissions(mContext, mPid, mUid, requiredPermissions);
}
+
+ private void acquireWakeLock() {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ if (mIsRegistered.get()) {
+ mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
+ }
+ });
+ }
+
+ private void releaseWakeLock() {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ if (mWakeLock.isHeld()) {
+ try {
+ mWakeLock.release();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Releasing the wakelock fails - ", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Invokes a callback and acquires a wakelock.
+ *
+ * @param consumer The callback invoke
+ * @return false if the callback threw a RemoteException
+ */
+ private boolean invokeCallback(CallbackConsumer consumer) {
+ if (mContextHubEndpointCallback != null) {
+ acquireWakeLock();
+ try {
+ consumer.accept(mContextHubEndpointCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling endpoint callback", e);
+ releaseWakeLock();
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING
index 43e2afd8827d..dbf9915c6e0c 100644
--- a/services/core/java/com/android/server/media/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/TEST_MAPPING
@@ -1,7 +1,10 @@
{
"presubmit": [
{
- "name": "CtsMediaBetterTogetherTestCases"
+ "name": "CtsMediaRouterTestCases"
+ },
+ {
+ "name": "CtsMediaSessionTestCases"
},
{
"name": "MediaRouterServiceTests"
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9567c818fa18..dd9741ce9ca1 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3162,6 +3162,7 @@ public class NotificationManagerService extends SystemService {
mAssistants.onBootPhaseAppsCanStart();
mConditionProviders.onBootPhaseAppsCanStart();
mHistoryManager.onBootPhaseAppsCanStart();
+ mPreferencesHelper.onBootPhaseAppsCanStart();
migrateDefaultNAS();
maybeShowInitialReviewPermissionsNotification();
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 93f512bc7e17..0bb3c6a067e3 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -36,10 +36,7 @@ import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.Person;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
@@ -48,7 +45,6 @@ import android.media.AudioAttributes;
import android.media.AudioSystem;
import android.metrics.LogMaker;
import android.net.Uri;
-import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -1493,23 +1489,14 @@ public final class NotificationRecord {
final Notification notification = getNotification();
notification.visitUris((uri) -> {
- if (com.android.server.notification.Flags.notificationVerifyChannelSoundUri()) {
- visitGrantableUri(uri, false, false);
- } else {
- oldVisitGrantableUri(uri, false, false);
- }
+ visitGrantableUri(uri, false, false);
});
if (notification.getChannelId() != null) {
NotificationChannel channel = getChannel();
if (channel != null) {
- if (com.android.server.notification.Flags.notificationVerifyChannelSoundUri()) {
- visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
- } else {
- oldVisitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
- }
+ visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
}
}
} finally {
@@ -1525,53 +1512,6 @@ public final class NotificationRecord {
* {@link #mGrantableUris}. Otherwise, this will either log or throw
* {@link SecurityException} depending on target SDK of enqueuing app.
*/
- private void oldVisitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
- if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
-
- if (mGrantableUris != null && mGrantableUris.contains(uri)) {
- return; // already verified this URI
- }
-
- final int sourceUid = getSbn().getUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- // This will throw a SecurityException if the caller can't grant.
- mUgmInternal.checkGrantUriPermission(sourceUid, null,
- ContentProvider.getUriWithoutUserId(uri),
- Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
-
- if (mGrantableUris == null) {
- mGrantableUris = new ArraySet<>();
- }
- mGrantableUris.add(uri);
- } catch (SecurityException e) {
- if (!userOverriddenUri) {
- if (isSound) {
- mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
- Log.w(TAG, "Replacing " + uri + " from " + sourceUid + ": " + e.getMessage());
- } else {
- if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
- throw e;
- } else {
- Log.w(TAG,
- "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
- }
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Note the presence of a {@link Uri} that should have permission granted to
- * whoever will be rendering it.
- * <p>
- * If the enqueuing app has the ability to grant access, it will be added to
- * {@link #mGrantableUris}. Otherwise, this will either log or throw
- * {@link SecurityException} depending on target SDK of enqueuing app.
- */
private void visitGrantableUri(Uri uri, boolean userOverriddenUri,
boolean isSound) {
if (mGrantableUris != null && mGrantableUris.contains(uri)) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 15377d6b269a..36eabae69b22 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -82,7 +82,6 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -272,6 +271,15 @@ public class PreferencesHelper implements RankingConfig {
updateMediaNotificationFilteringEnabled();
}
+ void onBootPhaseAppsCanStart() {
+ // IpcDataCaches must be invalidated once data becomes available, as queries will only
+ // begin to be cached after the first invalidation signal. At this point, we know about all
+ // notification channels.
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
+ }
+
public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
throws XmlPullParserException, IOException {
int type = parser.getEventType();
@@ -531,12 +539,14 @@ public class PreferencesHelper implements RankingConfig {
private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
@UserIdInt int userId, int uid, int importance, int priority, int visibility,
boolean showBadge, int bubblePreference, long creationTime) {
+ boolean created = false;
final String key = packagePreferencesKey(pkg, uid);
PackagePreferences
r = (uid == UNKNOWN_UID)
? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId))
: mPackagePreferences.get(key);
if (r == null) {
+ created = true;
r = new PackagePreferences();
r.pkg = pkg;
r.uid = uid;
@@ -572,6 +582,9 @@ public class PreferencesHelper implements RankingConfig {
mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId));
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && created) {
+ invalidateNotificationChannelCache();
+ }
return r;
}
@@ -664,6 +677,9 @@ public class PreferencesHelper implements RankingConfig {
}
NotificationChannel channel = new NotificationChannel(channelId, label, IMPORTANCE_LOW);
p.channels.put(channelId, channel);
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
return channel;
}
@@ -1171,9 +1187,7 @@ public class PreferencesHelper implements RankingConfig {
// Verify that the app has permission to read the sound Uri
// Only check for new channels, as regular apps can only set sound
// before creating. See: {@link NotificationChannel#setSound}
- if (Flags.notificationVerifyChannelSoundUri()) {
- PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid);
- }
+ PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid);
channel.setImportanceLockedByCriticalDeviceFunction(
r.defaultAppLockedImportance || r.fixedImportance);
@@ -1208,6 +1222,10 @@ public class PreferencesHelper implements RankingConfig {
updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && needsPolicyFileChange) {
+ invalidateNotificationChannelCache();
+ }
+
return needsPolicyFileChange;
}
@@ -1229,6 +1247,9 @@ public class PreferencesHelper implements RankingConfig {
}
channel.unlockFields(USER_LOCKED_IMPORTANCE);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
@@ -1301,6 +1322,9 @@ public class PreferencesHelper implements RankingConfig {
updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
}
if (changed) {
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
updateConfig();
}
}
@@ -1537,6 +1561,10 @@ public class PreferencesHelper implements RankingConfig {
if (channelBypassedDnd) {
updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
}
+
+ if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannel) {
+ invalidateNotificationChannelCache();
+ }
return deletedChannel;
}
@@ -1566,6 +1594,9 @@ public class PreferencesHelper implements RankingConfig {
}
r.channels.remove(channelId);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
@Override
@@ -1576,13 +1607,18 @@ public class PreferencesHelper implements RankingConfig {
if (r == null) {
return;
}
+ boolean deleted = false;
int N = r.channels.size() - 1;
for (int i = N; i >= 0; i--) {
String key = r.channels.keyAt(i);
if (!DEFAULT_CHANNEL_ID.equals(key)) {
r.channels.remove(key);
+ deleted = true;
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && deleted) {
+ invalidateNotificationChannelCache();
+ }
}
}
@@ -1613,6 +1649,9 @@ public class PreferencesHelper implements RankingConfig {
}
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
public void updateDefaultApps(int userId, ArraySet<String> toRemove,
@@ -1642,6 +1681,9 @@ public class PreferencesHelper implements RankingConfig {
}
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
@@ -1757,6 +1799,9 @@ public class PreferencesHelper implements RankingConfig {
if (groupBypassedDnd) {
updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannels.size() > 0) {
+ invalidateNotificationChannelCache();
+ }
return deletedChannels;
}
@@ -1902,8 +1947,13 @@ public class PreferencesHelper implements RankingConfig {
}
}
}
- if (!deletedChannelIds.isEmpty() && mCurrentUserHasChannelsBypassingDnd) {
- updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ if (!deletedChannelIds.isEmpty()) {
+ if (mCurrentUserHasChannelsBypassingDnd) {
+ updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ }
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
return deletedChannelIds;
}
@@ -2196,6 +2246,11 @@ public class PreferencesHelper implements RankingConfig {
PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid);
prefs.delegate = new Delegate(delegatePkg, delegateUid, true);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ // If package delegates change, then which packages can get what channel information
+ // also changes, so we need to clear the cache.
+ invalidateNotificationChannelCache();
+ }
}
/**
@@ -2208,6 +2263,9 @@ public class PreferencesHelper implements RankingConfig {
prefs.delegate.mEnabled = false;
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
/**
@@ -2811,18 +2869,24 @@ public class PreferencesHelper implements RankingConfig {
public void onUserRemoved(int userId) {
synchronized (mLock) {
+ boolean removed = false;
int N = mPackagePreferences.size();
for (int i = N - 1; i >= 0; i--) {
PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
mPackagePreferences.removeAt(i);
+ removed = true;
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && removed) {
+ invalidateNotificationChannelCache();
+ }
}
}
protected void onLocaleChanged(Context context, int userId) {
synchronized (mLock) {
+ boolean updated = false;
int N = mPackagePreferences.size();
for (int i = 0; i < N; i++) {
PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
@@ -2833,10 +2897,14 @@ public class PreferencesHelper implements RankingConfig {
DEFAULT_CHANNEL_ID).setName(
context.getResources().getString(
R.string.default_notification_channel_label));
+ updated = true;
}
// TODO (b/346396459): Localize all reserved channels
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && updated) {
+ invalidateNotificationChannelCache();
+ }
}
}
@@ -2884,7 +2952,7 @@ public class PreferencesHelper implements RankingConfig {
channel.getAudioAttributes().getUsage());
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
restoredUri)) {
- Log.w(TAG,
+ Slog.w(TAG,
"Could not restore sound: " + uri + " for channel: "
+ channel);
}
@@ -2922,6 +2990,9 @@ public class PreferencesHelper implements RankingConfig {
if (updated) {
updateConfig();
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
return updated;
}
@@ -2939,6 +3010,9 @@ public class PreferencesHelper implements RankingConfig {
p.priority = DEFAULT_PRIORITY;
p.visibility = DEFAULT_VISIBILITY;
p.showBadge = DEFAULT_SHOW_BADGE;
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
}
}
@@ -3123,6 +3197,9 @@ public class PreferencesHelper implements RankingConfig {
}
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
public void migrateNotificationPermissions(List<UserInfo> users) {
@@ -3154,6 +3231,12 @@ public class PreferencesHelper implements RankingConfig {
mRankingHandler.requestSort();
}
+ @VisibleForTesting
+ // Utility method for overriding in tests to confirm that the cache gets cleared.
+ protected void invalidateNotificationChannelCache() {
+ NotificationManager.invalidateNotificationChannelCache();
+ }
+
private static String packagePreferencesKey(String pkg, int uid) {
return pkg + "|" + uid;
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 2b4d71e85dc0..c1ca9c23aef5 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -172,16 +172,6 @@ flag {
}
flag {
- name: "notification_verify_channel_sound_uri"
- namespace: "systemui"
- description: "Verify Uri permission for sound when creating a notification channel"
- bug: "337775777"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "notification_vibration_in_sound_uri_for_channel"
namespace: "systemui"
description: "Enables sound uri with vibration source in notification channel"
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 17d7a14d9129..e1b76222072e 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -612,7 +612,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
final PackageSetting staticLibPkgSetting =
mPm.getPackageSettingForMutation(sharedLibraryInfo.getPackageName());
if (staticLibPkgSetting == null) {
- Slog.wtf(TAG, "Shared lib without setting: " + sharedLibraryInfo);
+ Slog.w(TAG, "Shared lib without setting: " + sharedLibraryInfo);
continue;
}
for (int u = 0; u < installedUserCount; u++) {
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index 7a5a14d8d3c2..b32943704dc4 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -293,8 +293,8 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt
if (mOverlayPaths == null && mSharedLibraryOverlayPaths == null) {
return null;
}
- final OverlayPaths.Builder newPaths = new OverlayPaths.Builder();
- newPaths.addAll(mOverlayPaths);
+ final OverlayPaths.Builder newPaths = mOverlayPaths == null
+ ? new OverlayPaths.Builder() : new OverlayPaths.Builder(mOverlayPaths);
if (mSharedLibraryOverlayPaths != null) {
for (final OverlayPaths libOverlayPaths : mSharedLibraryOverlayPaths.values()) {
newPaths.addAll(libOverlayPaths);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 516213b32354..7f511e1e2aa1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -85,15 +85,16 @@ import static android.view.contentprotection.flags.Flags.createAccessibilityOver
import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.GestureLauncherService.DOUBLE_POWER_TAP_COUNT_THRESHOLD;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -502,6 +503,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private TalkbackShortcutController mTalkbackShortcutController;
+ private VoiceAccessShortcutController mVoiceAccessShortcutController;
+
private WindowWakeUpPolicy mWindowWakeUpPolicy;
/**
@@ -2265,6 +2268,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return new TalkbackShortcutController(mContext);
}
+ VoiceAccessShortcutController getVoiceAccessShortcutController() {
+ return new VoiceAccessShortcutController(mContext);
+ }
+
WindowWakeUpPolicy getWindowWakeUpPolicy() {
return new WindowWakeUpPolicy(mContext);
}
@@ -2512,6 +2519,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
mTalkbackShortcutController = injector.getTalkbackShortcutController();
+ mVoiceAccessShortcutController = injector.getVoiceAccessShortcutController();
mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy();
initKeyCombinationRules();
initSingleKeyGestureRules(injector.getLooper());
@@ -4262,6 +4270,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
.isAccessibilityShortcutAvailable(false);
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
return enableTalkbackAndMagnifierKeyGestures();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ return enableVoiceAccessKeyGestures();
default:
return false;
}
@@ -4492,6 +4502,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ if (enableVoiceAccessKeyGestures()) {
+ if (complete) {
+ mVoiceAccessShortcutController.toggleVoiceAccess(mCurrentUserId);
+ }
+ return true;
+ }
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
AppLaunchData data = event.getAppLaunchData();
if (complete && canLaunchApp && data != null
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
index 9e16a7d5e83a..efda337527d4 100644
--- a/services/core/java/com/android/server/policy/TalkbackShortcutController.java
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -18,20 +18,15 @@ package com.android.server.policy;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
import android.os.UserHandle;
import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.List;
import java.util.Set;
/**
@@ -42,7 +37,6 @@ import java.util.Set;
class TalkbackShortcutController {
private static final String TALKBACK_LABEL = "TalkBack";
private final Context mContext;
- private final PackageManager mPackageManager;
public enum ShortcutSource {
GESTURE,
@@ -51,7 +45,6 @@ class TalkbackShortcutController {
TalkbackShortcutController(Context context) {
mContext = context;
- mPackageManager = mContext.getPackageManager();
}
/**
@@ -63,7 +56,10 @@ class TalkbackShortcutController {
boolean toggleTalkback(int userId, ShortcutSource source) {
final Set<ComponentName> enabledServices =
AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
- ComponentName componentName = getTalkbackComponent();
+ ComponentName componentName =
+ AccessibilityUtils.getInstalledAccessibilityServiceComponentNameByLabel(
+ mContext, TALKBACK_LABEL);
+ ;
if (componentName == null) {
return false;
}
@@ -83,21 +79,6 @@ class TalkbackShortcutController {
return isTalkbackAlreadyEnabled;
}
- private ComponentName getTalkbackComponent() {
- AccessibilityManager accessibilityManager = mContext.getSystemService(
- AccessibilityManager.class);
- List<AccessibilityServiceInfo> serviceInfos =
- accessibilityManager.getInstalledAccessibilityServiceList();
-
- for (AccessibilityServiceInfo service : serviceInfos) {
- final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
- if (isTalkback(serviceInfo)) {
- return new ComponentName(serviceInfo.packageName, serviceInfo.name);
- }
- }
- return null;
- }
-
boolean isTalkBackShortcutGestureEnabled() {
return Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
@@ -120,9 +101,4 @@ class TalkbackShortcutController {
ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE,
/* serviceEnabled= */ true);
}
-
- private boolean isTalkback(ServiceInfo info) {
- return TALKBACK_LABEL.equals(info.loadLabel(mPackageManager).toString())
- && (info.applicationInfo.isSystemApp() || info.applicationInfo.isUpdatedSystemApp());
- }
}
diff --git a/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java b/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java
new file mode 100644
index 000000000000..a37fb1140e06
--- /dev/null
+++ b/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.internal.accessibility.util.AccessibilityUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Set;
+
+/** This class controls voice access shortcut related operations such as toggling, querying. */
+class VoiceAccessShortcutController {
+ private static final String TAG = VoiceAccessShortcutController.class.getSimpleName();
+ private static final String VOICE_ACCESS_LABEL = "Voice Access";
+
+ private final Context mContext;
+
+ VoiceAccessShortcutController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * A function that toggles voice access service.
+ *
+ * @return whether voice access is enabled after being toggled.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ boolean toggleVoiceAccess(int userId) {
+ final Set<ComponentName> enabledServices =
+ AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
+ ComponentName componentName =
+ AccessibilityUtils.getInstalledAccessibilityServiceComponentNameByLabel(
+ mContext, VOICE_ACCESS_LABEL);
+ if (componentName == null) {
+ Slog.e(TAG, "Toggle Voice Access failed due to componentName being null");
+ return false;
+ }
+
+ boolean newState = !enabledServices.contains(componentName);
+ AccessibilityUtils.setAccessibilityServiceState(mContext, componentName, newState, userId);
+
+ return newState;
+ }
+}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 14539d544bf9..50db1e4ac30e 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -84,8 +84,12 @@ class RollbackStore {
*/
private static List<Rollback> loadRollbacks(File rollbackDataDir) {
List<Rollback> rollbacks = new ArrayList<>();
- rollbackDataDir.mkdirs();
- for (File rollbackDir : rollbackDataDir.listFiles()) {
+ File[] rollbackDirs = rollbackDataDir.listFiles();
+ if (rollbackDirs == null) {
+ Slog.e(TAG, "Folder doesn't exist: " + rollbackDataDir);
+ return rollbacks;
+ }
+ for (File rollbackDir : rollbackDirs) {
if (rollbackDir.isDirectory()) {
try {
rollbacks.add(loadRollback(rollbackDir));
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index 2088e411f842..383135233049 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -142,11 +142,8 @@ class AggregatedMobileDataStatsPuller {
private final RateLimiter mRateLimiter;
AggregatedMobileDataStatsPuller(@NonNull NetworkStatsManager networkStatsManager) {
- if (DEBUG) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
- TAG + "-AggregatedMobileDataStatsPullerInit");
- }
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-Init");
}
mRateLimiter = new RateLimiter(/* window= */ Duration.ofSeconds(1));
@@ -173,10 +170,16 @@ class AggregatedMobileDataStatsPuller {
public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
long unusedUptime) {
- mMobileDataStatsHandler.post(
+ if (mRateLimiter.tryAcquire()) {
+ mMobileDataStatsHandler.post(
() -> {
noteUidProcessStateImpl(uid, state);
});
+ } else {
+ synchronized (mLock) {
+ mUidPreviousState.put(uid, state);
+ }
+ }
}
public int pullDataBytesTransfer(List<StatsEvent> data) {
@@ -209,29 +212,27 @@ class AggregatedMobileDataStatsPuller {
}
private void noteUidProcessStateImpl(int uid, int state) {
- if (mRateLimiter.tryAcquire()) {
- // noteUidProcessStateImpl can be called back to back several times while
- // the updateNetworkStats loops over several stats for multiple uids
- // and during the first call in a batch of proc state change event it can
- // contain info for uid with unknown previous state yet which can happen due to a few
- // reasons:
- // - app was just started
- // - app was started before the ActivityManagerService
- // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
- if (mNetworkStatsManager != null) {
- updateNetworkStats(mNetworkStatsManager);
- } else {
- Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
- }
+ // noteUidProcessStateImpl can be called back to back several times while
+ // the updateNetworkStats loops over several stats for multiple uids
+ // and during the first call in a batch of proc state change event it can
+ // contain info for uid with unknown previous state yet which can happen due to a few
+ // reasons:
+ // - app was just started
+ // - app was started before the ActivityManagerService
+ // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ } else {
+ Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+ }
+ synchronized (mLock) {
+ mUidPreviousState.put(uid, state);
}
- mUidPreviousState.put(uid, state);
}
private void updateNetworkStats(NetworkStatsManager networkStatsManager) {
- if (DEBUG) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
- }
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
}
final NetworkStats latestStats = networkStatsManager.getMobileUidStats();
@@ -256,20 +257,25 @@ class AggregatedMobileDataStatsPuller {
}
private void updateNetworkStatsDelta(NetworkStats delta) {
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStatsDelta");
+ }
synchronized (mLock) {
for (NetworkStats.Entry entry : delta) {
- if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
- continue;
- }
- MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
- if (stats != null) {
- stats.addTxBytes(entry.getTxBytes());
- stats.addRxBytes(entry.getRxBytes());
- stats.addTxPackets(entry.getTxPackets());
- stats.addRxPackets(entry.getRxPackets());
+ if (entry.getRxPackets() != 0 || entry.getTxPackets() != 0) {
+ MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
+ if (stats != null) {
+ stats.addTxBytes(entry.getTxBytes());
+ stats.addRxBytes(entry.getRxBytes());
+ stats.addTxPackets(entry.getTxPackets());
+ stats.addRxPackets(entry.getRxPackets());
+ }
}
}
}
+ if (DEBUG) {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
}
@GuardedBy("mLock")
@@ -298,18 +304,12 @@ class AggregatedMobileDataStatsPuller {
}
private static boolean isEmpty(NetworkStats stats) {
- long totalRxPackets = 0;
- long totalTxPackets = 0;
for (NetworkStats.Entry entry : stats) {
- if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
- continue;
+ if (entry.getRxPackets() != 0 || entry.getTxPackets() != 0) {
+ // at least one non empty entry located
+ return false;
}
- totalRxPackets += entry.getRxPackets();
- totalTxPackets += entry.getTxPackets();
- // at least one non empty entry located
- break;
}
- final long totalPackets = totalRxPackets + totalTxPackets;
- return totalPackets == 0;
+ return true;
}
}
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 2049a0288f5a..b651c7ba34c3 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -72,8 +72,12 @@ public final class ServerFlags {
KEY_TIME_ZONE_DETECTOR_AUTO_DETECTION_ENABLED_DEFAULT,
KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+ KEY_TIME_ZONE_NOTIFICATIONS_SUPPORTED,
+ KEY_TIME_ZONE_NOTIFICATIONS_ENABLED_DEFAULT,
+ KEY_TIME_ZONE_NOTIFICATIONS_TRACKING_SUPPORTED,
+ KEY_TIME_ZONE_MANUAL_CHANGE_TRACKING_SUPPORTED
})
- @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
+ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.SOURCE)
@interface DeviceConfigKey {}
@@ -192,6 +196,31 @@ public final class ServerFlags {
"enhanced_metrics_collection_enabled";
/**
+ * The key to control support for time zone notifications under certain circumstances.
+ */
+ public static final @DeviceConfigKey String KEY_TIME_ZONE_NOTIFICATIONS_SUPPORTED =
+ "time_zone_notifications_supported";
+
+ /**
+ * The key for the default value used to determine whether time zone notifications is enabled
+ * when the user hasn't explicitly set it yet.
+ */
+ public static final @DeviceConfigKey String KEY_TIME_ZONE_NOTIFICATIONS_ENABLED_DEFAULT =
+ "time_zone_notifications_enabled_default";
+
+ /**
+ * The key to control support for time zone notifications tracking under certain circumstances.
+ */
+ public static final @DeviceConfigKey String KEY_TIME_ZONE_NOTIFICATIONS_TRACKING_SUPPORTED =
+ "time_zone_notifications_tracking_supported";
+
+ /**
+ * The key to control support for time zone manual change tracking under certain circumstances.
+ */
+ public static final @DeviceConfigKey String KEY_TIME_ZONE_MANUAL_CHANGE_TRACKING_SUPPORTED =
+ "time_zone_manual_change_tracking_supported";
+
+ /**
* The registered listeners and the keys to trigger on. The value is explicitly a HashSet to
* ensure O(1) lookup performance when working out whether a listener should trigger.
*/
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 3579246b660f..0495f54cb154 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -286,7 +286,8 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
// This check is racey, but the whole settings update process is racey. This check prevents
// a ConfigurationChangeListener callback triggering due to ContentObserver's still
// triggering *sometimes* for no-op updates. Because callbacks are async this is necessary
- // for stable behavior during tests.
+ // for stable behavior during tests. This behavior is copied from
+ // setAutoDetectionEnabledIfRequired and assumed to be the correct way.
if (getAutoDetectionEnabledSetting() != enabled) {
Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME, enabled ? 1 : 0);
}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index fc659c5cb627..c4c86a429dd6 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -65,6 +65,10 @@ public final class ConfigurationInternal {
private final boolean mUserConfigAllowed;
private final boolean mLocationEnabledSetting;
private final boolean mGeoDetectionEnabledSetting;
+ private final boolean mNotificationsSupported;
+ private final boolean mNotificationsEnabledSetting;
+ private final boolean mNotificationTrackingSupported;
+ private final boolean mManualChangeTrackingSupported;
private ConfigurationInternal(Builder builder) {
mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
@@ -78,6 +82,10 @@ public final class ConfigurationInternal {
mUserConfigAllowed = builder.mUserConfigAllowed;
mLocationEnabledSetting = builder.mLocationEnabledSetting;
mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
+ mNotificationsSupported = builder.mNotificationsSupported;
+ mNotificationsEnabledSetting = builder.mNotificationsEnabledSetting;
+ mNotificationTrackingSupported = builder.mNotificationsTrackingSupported;
+ mManualChangeTrackingSupported = builder.mManualChangeTrackingSupported;
}
/** Returns true if the device supports any form of auto time zone detection. */
@@ -104,6 +112,27 @@ public final class ConfigurationInternal {
}
/**
+ * Returns true if the device supports time-related notifications.
+ */
+ public boolean areNotificationsSupported() {
+ return mNotificationsSupported;
+ }
+
+ /**
+ * Returns true if the device supports tracking of time-related notifications.
+ */
+ public boolean isNotificationTrackingSupported() {
+ return areNotificationsSupported() && mNotificationTrackingSupported;
+ }
+
+ /**
+ * Returns true if the device supports tracking of time zone manual changes.
+ */
+ public boolean isManualChangeTrackingSupported() {
+ return mManualChangeTrackingSupported;
+ }
+
+ /**
* Returns {@code true} if location time zone detection should run when auto time zone detection
* is enabled on supported devices, even when the user has not enabled the algorithm explicitly
* in settings. Enabled for internal testing only. See {@link #isGeoDetectionExecutionEnabled()}
@@ -223,6 +252,15 @@ public final class ConfigurationInternal {
&& getGeoDetectionRunInBackgroundEnabledSetting();
}
+ /** Returns true if time-related notifications can be shown on this device. */
+ public boolean getNotificationsEnabledBehavior() {
+ return areNotificationsSupported() && getNotificationsEnabledSetting();
+ }
+
+ private boolean getNotificationsEnabledSetting() {
+ return mNotificationsEnabledSetting;
+ }
+
@NonNull
public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
UserHandle userHandle = UserHandle.of(mUserId);
@@ -283,6 +321,14 @@ public final class ConfigurationInternal {
}
builder.setSetManualTimeZoneCapability(suggestManualTimeZoneCapability);
+ final @CapabilityState int configureNotificationsEnabledCapability;
+ if (areNotificationsSupported()) {
+ configureNotificationsEnabledCapability = CAPABILITY_POSSESSED;
+ } else {
+ configureNotificationsEnabledCapability = CAPABILITY_NOT_SUPPORTED;
+ }
+ builder.setConfigureNotificationsEnabledCapability(configureNotificationsEnabledCapability);
+
return builder.build();
}
@@ -291,6 +337,7 @@ public final class ConfigurationInternal {
return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
.setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
+ .setNotificationsEnabled(getNotificationsEnabledSetting())
.build();
}
@@ -307,6 +354,9 @@ public final class ConfigurationInternal {
if (newConfiguration.hasIsGeoDetectionEnabled()) {
builder.setGeoDetectionEnabledSetting(newConfiguration.isGeoDetectionEnabled());
}
+ if (newConfiguration.hasIsNotificationsEnabled()) {
+ builder.setNotificationsEnabledSetting(newConfiguration.areNotificationsEnabled());
+ }
return builder.build();
}
@@ -328,7 +378,11 @@ public final class ConfigurationInternal {
&& mEnhancedMetricsCollectionEnabled == that.mEnhancedMetricsCollectionEnabled
&& mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
&& mLocationEnabledSetting == that.mLocationEnabledSetting
- && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
+ && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting
+ && mNotificationsSupported == that.mNotificationsSupported
+ && mNotificationsEnabledSetting == that.mNotificationsEnabledSetting
+ && mNotificationTrackingSupported == that.mNotificationTrackingSupported
+ && mManualChangeTrackingSupported == that.mManualChangeTrackingSupported;
}
@Override
@@ -336,7 +390,9 @@ public final class ConfigurationInternal {
return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
mGeoDetectionSupported, mTelephonyFallbackSupported,
mGeoDetectionRunInBackgroundEnabled, mEnhancedMetricsCollectionEnabled,
- mAutoDetectionEnabledSetting, mLocationEnabledSetting, mGeoDetectionEnabledSetting);
+ mAutoDetectionEnabledSetting, mLocationEnabledSetting, mGeoDetectionEnabledSetting,
+ mNotificationsSupported, mNotificationsEnabledSetting,
+ mNotificationTrackingSupported, mManualChangeTrackingSupported);
}
@Override
@@ -352,6 +408,10 @@ public final class ConfigurationInternal {
+ ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
+ ", mLocationEnabledSetting=" + mLocationEnabledSetting
+ ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
+ + ", mNotificationsSupported=" + mNotificationsSupported
+ + ", mNotificationsEnabledSetting=" + mNotificationsEnabledSetting
+ + ", mNotificationTrackingSupported=" + mNotificationTrackingSupported
+ + ", mManualChangeTrackingSupported=" + mManualChangeTrackingSupported
+ '}';
}
@@ -370,6 +430,10 @@ public final class ConfigurationInternal {
private boolean mAutoDetectionEnabledSetting;
private boolean mLocationEnabledSetting;
private boolean mGeoDetectionEnabledSetting;
+ private boolean mNotificationsSupported;
+ private boolean mNotificationsEnabledSetting;
+ private boolean mNotificationsTrackingSupported;
+ private boolean mManualChangeTrackingSupported;
/**
* Creates a new Builder.
@@ -390,6 +454,10 @@ public final class ConfigurationInternal {
this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
+ this.mNotificationsSupported = toCopy.mNotificationsSupported;
+ this.mNotificationsEnabledSetting = toCopy.mNotificationsEnabledSetting;
+ this.mNotificationsTrackingSupported = toCopy.mNotificationTrackingSupported;
+ this.mManualChangeTrackingSupported = toCopy.mManualChangeTrackingSupported;
}
/**
@@ -475,6 +543,38 @@ public final class ConfigurationInternal {
return this;
}
+ /**
+ * Sets the value of the time notification setting for this user.
+ */
+ public Builder setNotificationsEnabledSetting(boolean enabled) {
+ mNotificationsEnabledSetting = enabled;
+ return this;
+ }
+
+ /**
+ * Sets whether time zone notifications are supported on this device.
+ */
+ public Builder setNotificationsSupported(boolean enabled) {
+ mNotificationsSupported = enabled;
+ return this;
+ }
+
+ /**
+ * Sets whether time zone notification tracking is supported on this device.
+ */
+ public Builder setNotificationsTrackingSupported(boolean supported) {
+ mNotificationsTrackingSupported = supported;
+ return this;
+ }
+
+ /**
+ * Sets whether time zone manual change tracking are supported on this device.
+ */
+ public Builder setManualChangeTrackingSupported(boolean supported) {
+ mManualChangeTrackingSupported = supported;
+ return this;
+ }
+
/** Returns a new {@link ConfigurationInternal}. */
@NonNull
public ConfigurationInternal build() {
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index f1248a3f5f0e..d809fc6b6eea 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -68,7 +68,11 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
ServerFlags.KEY_TIME_ZONE_DETECTOR_AUTO_DETECTION_ENABLED_DEFAULT,
- ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED
+ ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
+ ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_SUPPORTED,
+ ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_ENABLED_DEFAULT,
+ ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_TRACKING_SUPPORTED,
+ ServerFlags.KEY_TIME_ZONE_MANUAL_CHANGE_TRACKING_SUPPORTED
);
/**
@@ -100,11 +104,16 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
@Nullable
private static ServiceConfigAccessor sInstance;
- @NonNull private final Context mContext;
- @NonNull private final ServerFlags mServerFlags;
- @NonNull private final ContentResolver mCr;
- @NonNull private final UserManager mUserManager;
- @NonNull private final LocationManager mLocationManager;
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final ServerFlags mServerFlags;
+ @NonNull
+ private final ContentResolver mCr;
+ @NonNull
+ private final UserManager mUserManager;
+ @NonNull
+ private final LocationManager mLocationManager;
@GuardedBy("this")
@NonNull
@@ -193,6 +202,9 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE_EXPLICIT), true,
contentObserver);
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.TIME_ZONE_NOTIFICATIONS), true,
+ contentObserver);
// Add async callbacks for user scoped location settings being changed.
contentResolver.registerContentObserver(
@@ -331,6 +343,14 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
setGeoDetectionEnabledSettingIfRequired(userId, geoDetectionEnabledSetting);
}
}
+
+ if (areNotificationsSupported()) {
+ if (requestedConfigurationUpdates.hasIsNotificationsEnabled()) {
+ setNotificationsEnabledSetting(
+ requestedConfigurationUpdates.areNotificationsEnabled());
+ }
+ setNotificationsEnabledIfRequired(newConfiguration.areNotificationsEnabled());
+ }
}
@Override
@@ -348,6 +368,10 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
.setUserConfigAllowed(isUserConfigAllowed(userId))
.setLocationEnabledSetting(getLocationEnabledSetting(userId))
.setGeoDetectionEnabledSetting(getGeoDetectionEnabledSetting(userId))
+ .setNotificationsSupported(areNotificationsSupported())
+ .setNotificationsEnabledSetting(getNotificationsEnabledSetting())
+ .setNotificationsTrackingSupported(isNotificationTrackingSupported())
+ .setManualChangeTrackingSupported(isManualChangeTrackingSupported())
.build();
}
@@ -421,6 +445,49 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
}
}
+ private boolean areNotificationsSupported() {
+ return mServerFlags.getBoolean(
+ ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_SUPPORTED,
+ getConfigBoolean(R.bool.config_enableTimeZoneNotificationsSupported));
+ }
+
+ private boolean isNotificationTrackingSupported() {
+ return mServerFlags.getBoolean(
+ ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_TRACKING_SUPPORTED,
+ getConfigBoolean(R.bool.config_enableTimeZoneNotificationsTrackingSupported));
+ }
+
+ private boolean isManualChangeTrackingSupported() {
+ return mServerFlags.getBoolean(
+ ServerFlags.KEY_TIME_ZONE_MANUAL_CHANGE_TRACKING_SUPPORTED,
+ getConfigBoolean(R.bool.config_enableTimeZoneManualChangeTrackingSupported));
+ }
+
+ private boolean getNotificationsEnabledSetting() {
+ final boolean notificationsEnabledByDefault = areNotificationsEnabledByDefault();
+ return Settings.Global.getInt(mCr, Settings.Global.TIME_ZONE_NOTIFICATIONS,
+ (notificationsEnabledByDefault ? 1 : 0) /* defaultValue */) != 0;
+ }
+
+ private boolean areNotificationsEnabledByDefault() {
+ return mServerFlags.getBoolean(
+ ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_ENABLED_DEFAULT, true);
+ }
+
+ private void setNotificationsEnabledSetting(boolean enabled) {
+ Settings.Global.putInt(mCr, Settings.Global.TIME_ZONE_NOTIFICATIONS, enabled ? 1 : 0);
+ }
+
+ private void setNotificationsEnabledIfRequired(boolean enabled) {
+ // This check is racey, but the whole settings update process is racey. This check prevents
+ // a ConfigurationChangeListener callback triggering due to ContentObserver's still
+ // triggering *sometimes* for no-op updates. Because callbacks are async this is necessary
+ // for stable behavior during tests.
+ if (getNotificationsEnabledSetting() != enabled) {
+ Settings.Global.putInt(mCr, Settings.Global.TIME_ZONE_NOTIFICATIONS, enabled ? 1 : 0);
+ }
+ }
+
@Override
public void addLocationTimeZoneManagerConfigListener(
@NonNull StateChangeListener listener) {
@@ -441,8 +508,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
@Override
public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection);
+ return getConfigBoolean(R.bool.config_enableGeolocationTimeZoneDetection);
}
@Override
@@ -660,8 +726,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
private boolean isTelephonyFallbackSupported() {
return mServerFlags.getBoolean(
ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
- getConfigBoolean(
- com.android.internal.R.bool.config_supportTelephonyTimeZoneFallback));
+ getConfigBoolean(R.bool.config_supportTelephonyTimeZoneFallback));
}
private boolean getConfigBoolean(int providerEnabledConfigId) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneChangeListener.java b/services/core/java/com/android/server/timezonedetector/TimeZoneChangeListener.java
new file mode 100644
index 000000000000..e14326cc2d53
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneChangeListener.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.util.IndentingPrintWriter;
+
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
+import com.android.server.timezonedetector.TimeZoneDetectorStrategy.Origin;
+
+import java.util.Objects;
+
+public interface TimeZoneChangeListener {
+
+ /** Record a time zone change. */
+ void process(TimeZoneChangeEvent event);
+
+ /** Dump internal state. */
+ void dump(IndentingPrintWriter ipw);
+
+ class TimeZoneChangeEvent {
+
+ private final @ElapsedRealtimeLong long mElapsedRealtimeMillis;
+ private final @CurrentTimeMillisLong long mUnixEpochTimeMillis;
+ private final @Origin int mOrigin;
+ private final @UserIdInt int mUserId;
+ private final String mOldZoneId;
+ private final String mNewZoneId;
+ private final @TimeZoneConfidence int mNewConfidence;
+ private final String mCause;
+
+ public TimeZoneChangeEvent(@ElapsedRealtimeLong long elapsedRealtimeMillis,
+ @CurrentTimeMillisLong long unixEpochTimeMillis,
+ @Origin int origin, @UserIdInt int userId, @NonNull String oldZoneId,
+ @NonNull String newZoneId, int newConfidence, @NonNull String cause) {
+ mElapsedRealtimeMillis = elapsedRealtimeMillis;
+ mUnixEpochTimeMillis = unixEpochTimeMillis;
+ mOrigin = origin;
+ mUserId = userId;
+ mOldZoneId = Objects.requireNonNull(oldZoneId);
+ mNewZoneId = Objects.requireNonNull(newZoneId);
+ mNewConfidence = newConfidence;
+ mCause = Objects.requireNonNull(cause);
+ }
+
+ public @ElapsedRealtimeLong long getElapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
+
+ public @CurrentTimeMillisLong long getUnixEpochTimeMillis() {
+ return mUnixEpochTimeMillis;
+ }
+
+ public @Origin int getOrigin() {
+ return mOrigin;
+ }
+
+ /**
+ * The ID of the user that triggered the change.
+ *
+ * <p>If automatic time zone is turned on, the user ID returned is the system's user id.
+ */
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
+ public String getOldZoneId() {
+ return mOldZoneId;
+ }
+
+ public String getNewZoneId() {
+ return mNewZoneId;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneChangeEvent{"
+ + "mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+ + ", mUnixEpochTimeMillis=" + mUnixEpochTimeMillis
+ + ", mOrigin=" + mOrigin
+ + ", mUserId=" + mUserId
+ + ", mOldZoneId='" + mOldZoneId + '\''
+ + ", mNewZoneId='" + mNewZoneId + '\''
+ + ", mNewConfidence=" + mNewConfidence
+ + ", mCause='" + mCause + '\''
+ + '}';
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index d914544566ff..af02ad88ad6a 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -18,6 +18,7 @@ package com.android.server.timezonedetector;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.time.ITimeZoneDetectorListener;
import android.app.time.TimeZoneCapabilitiesAndConfig;
@@ -73,6 +74,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
@Override
+ @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
public void onStart() {
// Obtain / create the shared dependencies.
Context context = getContext();
@@ -81,7 +83,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
ServiceConfigAccessor serviceConfigAccessor =
ServiceConfigAccessorImpl.getInstance(context);
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
- TimeZoneDetectorStrategyImpl.create(handler, serviceConfigAccessor);
+ TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
DeviceActivityMonitor deviceActivityMonitor =
DeviceActivityMonitorImpl.create(context, handler);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 37e67c921634..8cfbe9daa970 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -15,6 +15,7 @@
*/
package com.android.server.timezonedetector;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilitiesAndConfig;
@@ -24,6 +25,11 @@ import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
/**
* The interface for the class that is responsible for setting the time zone on a device, used by
* {@link TimeZoneDetectorService} and {@link TimeZoneDetectorInternal}.
@@ -97,6 +103,22 @@ import android.util.IndentingPrintWriter;
* @hide
*/
public interface TimeZoneDetectorStrategy extends Dumpable {
+ @IntDef({ ORIGIN_UNKNOWN, ORIGIN_MANUAL, ORIGIN_TELEPHONY, ORIGIN_LOCATION })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
+ @interface Origin {}
+
+ /** Used when the origin of the time zone value cannot be inferred. */
+ @Origin int ORIGIN_UNKNOWN = 0;
+
+ /** Used when a time zone value originated from a user / manual settings. */
+ @Origin int ORIGIN_MANUAL = 1;
+
+ /** Used when a time zone value originated from a telephony signal. */
+ @Origin int ORIGIN_TELEPHONY = 2;
+
+ /** Used when a time zone value originated from a location signal. */
+ @Origin int ORIGIN_LOCATION = 3;
/**
* Adds a listener that will be triggered when something changes that could affect the result
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index dddb46f80724..19a28ddcdaeb 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -28,6 +28,7 @@ import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.time.DetectorStatusTypes;
import android.app.time.LocationTimeZoneAlgorithmStatus;
@@ -39,8 +40,10 @@ import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.content.Context;
import android.os.Handler;
import android.os.TimestampedValue;
+import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -72,12 +75,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* Returns the device's currently configured time zone. May return an empty string.
*/
- @NonNull String getDeviceTimeZone();
+ @NonNull
+ String getDeviceTimeZone();
/**
* Returns the confidence of the device's current time zone.
*/
- @TimeZoneConfidence int getDeviceTimeZoneConfidence();
+ @TimeZoneConfidence
+ int getDeviceTimeZoneConfidence();
/**
* Sets the device's time zone, associated confidence, and records a debug log entry.
@@ -115,7 +120,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* The abstract score for an empty or invalid telephony suggestion.
*
- * Used to score telephony suggestions where there is no zone.
+ * <p>Used to score telephony suggestions where there is no zone.
*/
@VisibleForTesting
public static final int TELEPHONY_SCORE_NONE = 0;
@@ -123,11 +128,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* The abstract score for a low quality telephony suggestion.
*
- * Used to score suggestions where:
- * The suggested zone ID is one of several possibilities, and the possibilities have different
- * offsets.
+ * <p>Used to score suggestions where:
+ * The suggested zone ID is one of several possibilities,
+ * and the possibilities have different offsets.
*
- * You would have to be quite desperate to want to use this choice.
+ * <p>You would have to be quite desperate to want to use this choice.
*/
@VisibleForTesting
public static final int TELEPHONY_SCORE_LOW = 1;
@@ -135,7 +140,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* The abstract score for a medium quality telephony suggestion.
*
- * Used for:
+ * <p>Used for:
* The suggested zone ID is one of several possibilities but at least the possibilities have the
* same offset. Users would get the correct time but for the wrong reason. i.e. their device may
* switch to DST at the wrong time and (for example) their calendar events.
@@ -146,7 +151,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* The abstract score for a high quality telephony suggestion.
*
- * Used for:
+ * <p>Used for:
* The suggestion was for one zone ID and the answer was unambiguous and likely correct given
* the info available.
*/
@@ -156,7 +161,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* The abstract score for a highest quality telephony suggestion.
*
- * Used for:
+ * <p>Used for:
* Suggestions that must "win" because they constitute test or emulator zone ID.
*/
@VisibleForTesting
@@ -206,7 +211,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
private final ServiceConfigAccessor mServiceConfigAccessor;
@GuardedBy("this")
- @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+ @NonNull
+ private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
/**
* A snapshot of the current detector status. A local copy is cached because it is relatively
@@ -244,8 +250,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
/**
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
*/
+ @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
public static TimeZoneDetectorStrategyImpl create(
- @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull ServiceConfigAccessor serviceConfigAccessor) {
Environment environment = new EnvironmentImpl(handler);
return new TimeZoneDetectorStrategyImpl(serviceConfigAccessor, environment);
@@ -468,7 +476,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// later disables automatic time zone detection.
mLatestManualSuggestion.set(suggestion);
- setDeviceTimeZoneIfRequired(timeZoneId, cause);
+ setDeviceTimeZoneIfRequired(timeZoneId, ORIGIN_MANUAL, userId, cause);
return true;
}
@@ -685,7 +693,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// GeolocationTimeZoneSuggestion has no measure of quality. We assume all suggestions are
// reliable.
- String zoneId;
+ String timeZoneId;
// Introduce bias towards the device's current zone when there are multiple zone suggested.
String deviceTimeZone = mEnvironment.getDeviceTimeZone();
@@ -694,11 +702,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
Slog.d(LOG_TAG,
"Geo tz suggestion contains current device time zone. Applying bias.");
}
- zoneId = deviceTimeZone;
+ timeZoneId = deviceTimeZone;
} else {
- zoneId = zoneIds.get(0);
+ timeZoneId = zoneIds.get(0);
}
- setDeviceTimeZoneIfRequired(zoneId, detectionReason);
+ setDeviceTimeZoneIfRequired(timeZoneId, ORIGIN_LOCATION, UserHandle.USER_SYSTEM,
+ detectionReason);
return true;
}
@@ -779,8 +788,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
// zone ID.
- String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
- if (zoneId == null) {
+ String timeZoneId = bestTelephonySuggestion.suggestion.getZoneId();
+ if (timeZoneId == null) {
Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+ " bestTelephonySuggestion=" + bestTelephonySuggestion
+ ", detectionReason=" + detectionReason);
@@ -790,11 +799,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
String cause = "Found good suggestion:"
+ " bestTelephonySuggestion=" + bestTelephonySuggestion
+ ", detectionReason=" + detectionReason;
- setDeviceTimeZoneIfRequired(zoneId, cause);
+ setDeviceTimeZoneIfRequired(timeZoneId, ORIGIN_TELEPHONY, UserHandle.USER_SYSTEM, cause);
}
@GuardedBy("this")
- private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
+ private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @Origin int origin,
+ @UserIdInt int userId, @NonNull String cause) {
String currentZoneId = mEnvironment.getDeviceTimeZone();
// All manual and automatic suggestions are considered high confidence as low-quality
// suggestions are not currently passed on.
diff --git a/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java b/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java
index 54ae047a2858..0b676ff7d590 100644
--- a/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java
+++ b/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java
@@ -100,6 +100,11 @@ final class BasicToPwleSegmentAdapter implements VibrationSegmentsAdapter {
}
VibratorInfo.FrequencyProfile frequencyProfile = info.getFrequencyProfile();
+ if (frequencyProfile.isEmpty()) {
+ // The frequency profile has an invalid frequency range, so keep the segments unchanged.
+ return repeatIndex;
+ }
+
float[] frequenciesHz = frequencyProfile.getFrequenciesHz();
float[] accelerationsGs = frequencyProfile.getOutputAccelerationsGs();
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 89c7a3d89a54..6f308aa9b706 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1631,7 +1631,7 @@ class ActivityMetricsLogger {
int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
if (isAppCompateStateChangedToLetterboxed(state)) {
- positionToLog = activity.mAppCompatController.getAppCompatReachabilityOverrides()
+ positionToLog = activity.mAppCompatController.getReachabilityOverrides()
.getLetterboxPositionForLogging();
}
FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 29f1f93a844f..3d53078de3c3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4261,7 +4261,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
void finishRelaunching() {
- mAppCompatController.getAppCompatOrientationOverrides()
+ mAppCompatController.getOrientationOverrides()
.setRelaunchingAfterRequestedOrientationChanged(false);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
@@ -8222,7 +8222,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLastReportedConfiguration.getMergedConfiguration())) {
ensureActivityConfiguration(false /* ignoreVisibility */);
if (mPendingRelaunchCount > originalRelaunchingCount) {
- mAppCompatController.getAppCompatOrientationOverrides()
+ mAppCompatController.getOrientationOverrides()
.setRelaunchingAfterRequestedOrientationChanged(true);
}
if (mTransitionController.inPlayingTransition(this)) {
@@ -8744,7 +8744,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
navBarInsets = Insets.NONE;
}
final AppCompatReachabilityOverrides reachabilityOverrides =
- mAppCompatController.getAppCompatReachabilityOverrides();
+ mAppCompatController.getReachabilityOverrides();
// Horizontal position
int offsetX = 0;
if (parentBounds.width() != screenResolvedBoundsWidth) {
@@ -10217,7 +10217,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mAppCompatController.getAppCompatAspectRatioOverrides()
.shouldOverrideMinAspectRatio());
proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
- mAppCompatController.getAppCompatOrientationOverrides()
+ mAppCompatController.getOrientationOverrides()
.shouldIgnoreOrientationRequestLoop());
proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
mAppCompatController.getResizeOverrides().shouldOverrideForceResizeApp());
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 0967078deac3..6d0e8eacd438 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -33,7 +33,7 @@ class AppCompatController {
@NonNull
private final AppCompatAspectRatioPolicy mAppCompatAspectRatioPolicy;
@NonNull
- private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
+ private final AppCompatReachabilityPolicy mReachabilityPolicy;
@NonNull
private final DesktopAppCompatAspectRatioPolicy mDesktopAppCompatAspectRatioPolicy;
@NonNull
@@ -58,7 +58,7 @@ class AppCompatController {
mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
mTransparentPolicy, mAppCompatOverrides);
- mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
+ mReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
wmService.mAppCompatConfiguration);
mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord,
wmService.mAppCompatConfiguration);
@@ -89,8 +89,8 @@ class AppCompatController {
}
@NonNull
- AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
- return mAppCompatOverrides.getAppCompatOrientationOverrides();
+ AppCompatOrientationOverrides getOrientationOverrides() {
+ return mAppCompatOverrides.getOrientationOverrides();
}
@NonNull
@@ -109,8 +109,8 @@ class AppCompatController {
}
@NonNull
- AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
- return mAppCompatReachabilityPolicy;
+ AppCompatReachabilityPolicy getReachabilityPolicy() {
+ return mReachabilityPolicy;
}
@NonNull
@@ -124,8 +124,8 @@ class AppCompatController {
}
@NonNull
- AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
- return mAppCompatOverrides.getAppCompatReachabilityOverrides();
+ AppCompatReachabilityOverrides getReachabilityOverrides() {
+ return mAppCompatOverrides.getReachabilityOverrides();
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index e929fb414340..449458665b63 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -154,7 +154,7 @@ class AppCompatLetterboxPolicy {
@VisibleForTesting
boolean shouldShowLetterboxUi(@NonNull WindowState mainWindow) {
- if (mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+ if (mActivityRecord.mAppCompatController.getOrientationOverrides()
.getIsRelaunchingAfterRequestedOrientationChanged()) {
return mLastShouldShowLetterboxUi;
}
@@ -205,7 +205,7 @@ class AppCompatLetterboxPolicy {
}
pw.println(prefix + " letterboxReason="
+ AppCompatUtils.getLetterboxReasonString(mActivityRecord, mainWin));
- mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy().dump(pw, prefix);
+ mActivityRecord.mAppCompatController.getReachabilityPolicy().dump(pw, prefix);
final AppCompatLetterboxOverrides letterboxOverride = mActivityRecord.mAppCompatController
.getAppCompatLetterboxOverrides();
pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString(
@@ -276,12 +276,12 @@ class AppCompatLetterboxPolicy {
final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
.mAppCompatController.getAppCompatLetterboxOverrides();
final AppCompatReachabilityPolicy reachabilityPolicy = mActivityRecord
- .mAppCompatController.getAppCompatReachabilityPolicy();
+ .mAppCompatController.getReachabilityPolicy();
mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
mActivityRecord.mWmService.mTransactionFactory,
reachabilityPolicy, letterboxOverrides,
this::getLetterboxParentSurface);
- mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+ mActivityRecord.mAppCompatController.getReachabilityPolicy()
.setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame);
}
final Point letterboxPosition = new Point();
@@ -291,7 +291,7 @@ class AppCompatLetterboxPolicy {
final Rect innerFrame = new Rect();
calculateLetterboxInnerBounds(mActivityRecord, w, innerFrame);
mLetterbox.layout(spaceToFill, innerFrame, letterboxPosition);
- if (mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides()
+ if (mActivityRecord.mAppCompatController.getReachabilityOverrides()
.isDoubleTapEvent()) {
// We need to notify Shell that letterbox position has changed.
mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
@@ -321,7 +321,7 @@ class AppCompatLetterboxPolicy {
mLetterbox.destroy();
mLetterbox = null;
}
- mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+ mActivityRecord.mAppCompatController.getReachabilityPolicy()
.setLetterboxInnerBoundsSupplier(null);
}
@@ -415,7 +415,7 @@ class AppCompatLetterboxPolicy {
calculateLetterboxPosition(mActivityRecord, mLetterboxPosition);
calculateLetterboxOuterBounds(mActivityRecord, mOuterBounds);
calculateLetterboxInnerBounds(mActivityRecord, w, mInnerBounds);
- mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+ mActivityRecord.mAppCompatController.getReachabilityPolicy()
.setLetterboxInnerBoundsSupplier(() -> mInnerBounds);
}
@@ -438,7 +438,7 @@ class AppCompatLetterboxPolicy {
mLetterboxPosition.set(0, 0);
mInnerBounds.setEmpty();
mOuterBounds.setEmpty();
- mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+ mActivityRecord.mAppCompatController.getReachabilityPolicy()
.setLetterboxInnerBoundsSupplier(null);
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index c84711d4be51..af83668f1188 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -113,7 +113,7 @@ class AppCompatOrientationOverrides {
// Task to ensure that Activity Embedding is excluded.
return mActivityRecord.isVisibleRequested() && mActivityRecord.getTaskFragment() != null
&& mActivityRecord.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+ && mActivityRecord.mAppCompatController.getOrientationOverrides()
.isOverrideRespectRequestedOrientationEnabled();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 16e20297dcf3..fc758ef90995 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -94,7 +94,7 @@ class AppCompatOrientationPolicy {
return SCREEN_ORIENTATION_PORTRAIT;
}
- if (mAppCompatOverrides.getAppCompatOrientationOverrides()
+ if (mAppCompatOverrides.getOrientationOverrides()
.isAllowOrientationOverrideOptOut()) {
return candidate;
}
@@ -108,7 +108,7 @@ class AppCompatOrientationPolicy {
}
final AppCompatOrientationOverrides.OrientationOverridesState capabilityState =
- mAppCompatOverrides.getAppCompatOrientationOverrides()
+ mAppCompatOverrides.getOrientationOverrides()
.mOrientationOverridesState;
if (capabilityState.mIsOverrideToReverseLandscapeOrientationEnabled
@@ -170,7 +170,7 @@ class AppCompatOrientationPolicy {
boolean shouldIgnoreRequestedOrientation(
@ActivityInfo.ScreenOrientation int requestedOrientation) {
final AppCompatOrientationOverrides orientationOverrides =
- mAppCompatOverrides.getAppCompatOrientationOverrides();
+ mAppCompatOverrides.getOrientationOverrides();
if (orientationOverrides.shouldEnableIgnoreOrientationRequest()) {
if (orientationOverrides.getIsRelaunchingAfterRequestedOrientationChanged()) {
Slog.w(TAG, "Ignoring orientation update to "
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 58b37becc373..9fb54db23d55 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -27,7 +27,7 @@ import com.android.server.wm.utils.OptPropFactory;
public class AppCompatOverrides {
@NonNull
- private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
+ private final AppCompatOrientationOverrides mOrientationOverrides;
@NonNull
private final AppCompatCameraOverrides mAppCompatCameraOverrides;
@NonNull
@@ -37,7 +37,7 @@ public class AppCompatOverrides {
@NonNull
private final AppCompatResizeOverrides mResizeOverrides;
@NonNull
- private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
+ private final AppCompatReachabilityOverrides mReachabilityOverrides;
@NonNull
private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides;
@@ -48,13 +48,13 @@ public class AppCompatOverrides {
@NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
- mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
+ mOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
- mAppCompatReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
+ mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
appCompatConfiguration, appCompatDeviceStateQuery);
mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
appCompatConfiguration, optPropBuilder, appCompatDeviceStateQuery,
- mAppCompatReachabilityOverrides);
+ mReachabilityOverrides);
mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager,
@@ -64,8 +64,8 @@ public class AppCompatOverrides {
}
@NonNull
- AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
- return mAppCompatOrientationOverrides;
+ AppCompatOrientationOverrides getOrientationOverrides() {
+ return mOrientationOverrides;
}
@NonNull
@@ -89,8 +89,8 @@ public class AppCompatOverrides {
}
@NonNull
- AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
- return mAppCompatReachabilityOverrides;
+ AppCompatReachabilityOverrides getReachabilityOverrides() {
+ return mReachabilityOverrides;
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
index d03a80387657..087edc184b6f 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -77,7 +77,7 @@ class AppCompatReachabilityPolicy {
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
final AppCompatReachabilityOverrides reachabilityOverrides =
- mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+ mActivityRecord.mAppCompatController.getReachabilityOverrides();
pw.println(prefix + " isVerticalThinLetterboxed=" + reachabilityOverrides
.isVerticalThinLetterboxed());
pw.println(prefix + " isHorizontalThinLetterboxed=" + reachabilityOverrides
@@ -96,7 +96,7 @@ class AppCompatReachabilityPolicy {
private void handleHorizontalDoubleTap(int x) {
final AppCompatReachabilityOverrides reachabilityOverrides =
- mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+ mActivityRecord.mAppCompatController.getReachabilityOverrides();
if (!reachabilityOverrides.isHorizontalReachabilityEnabled()
|| mActivityRecord.isInTransition()) {
return;
@@ -142,7 +142,7 @@ class AppCompatReachabilityPolicy {
private void handleVerticalDoubleTap(int y) {
final AppCompatReachabilityOverrides reachabilityOverrides =
- mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+ mActivityRecord.mAppCompatController.getReachabilityOverrides();
if (!reachabilityOverrides.isVerticalReachabilityEnabled()
|| mActivityRecord.isInTransition()) {
return;
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 9f88bc952351..e28dddc496e1 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -138,7 +138,7 @@ final class AppCompatUtils {
return;
}
final AppCompatReachabilityOverrides reachabilityOverrides = top.mAppCompatController
- .getAppCompatReachabilityOverrides();
+ .getReachabilityOverrides();
final boolean isTopActivityResumed = top.getOrganizedTask() == task && top.isState(RESUMED);
final boolean isTopActivityVisible = top.getOrganizedTask() == task && top.isVisible();
// Whether the direct top activity is in size compat mode.
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index a4e58ef923b8..d6ae65193121 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -108,9 +108,7 @@ final class ContentRecorder implements WindowContainerListener {
ContentRecorder(@NonNull DisplayContent displayContent) {
this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
- new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
- && !new DisplayManagerFlags()
- .isPixelAnisotropyCorrectionInLogicalDisplayEnabled()
+ !new DisplayManagerFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled()
&& displayContent.getDisplayInfo().type == Display.TYPE_EXTERNAL);
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f40d636b522a..b932ef362aca 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -264,7 +264,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
// that should be respected, Check all activities in display to make sure any eligible
// activity should be respected.
final ActivityRecord activity = mDisplayContent.getActivity((r) ->
- r.mAppCompatController.getAppCompatOrientationOverrides()
+ r.mAppCompatController.getOrientationOverrides()
.shouldRespectRequestedOrientationDueToOverride());
return activity != null;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 145c7b37fcdc..dd23f577e05b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2964,7 +2964,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (!handlesOrientationChangeFromDescendant(orientation)) {
ActivityRecord topActivity = topRunningActivity(/* considerKeyguardState= */ true);
if (topActivity != null && topActivity.mAppCompatController
- .getAppCompatOrientationOverrides()
+ .getOrientationOverrides()
.shouldUseDisplayLandscapeNaturalOrientation()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is ignoring orientation request for %d, return %d"
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f0faa8e4691f..a9646783b92d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1412,6 +1412,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (!tr.isAttached() || !tr.isVisibleRequested()
|| !tr.inPinnedWindowingMode()) return;
final ActivityRecord currTop = tr.getTopNonFinishingActivity();
+ if (currTop == null) return;
if (currTop.inPinnedWindowingMode()) return;
Slog.e(TAG, "Enter-PIP was started but not completed, this is a Shell/SysUI"
+ " bug. This state breaks gesture-nav, so attempting clean-up.");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3c6778ecbb30..92e0931993d1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -10177,9 +10177,10 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Access denied to process: " + pid
+ ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER);
}
-
- if (mRoot.anyTaskForId(taskId) == null) {
- throw new IllegalArgumentException("no task with taskId: " + taskId);
+ synchronized (mGlobalLock) {
+ if (mRoot.anyTaskForId(taskId) == null) {
+ throw new IllegalArgumentException("no task with taskId: " + taskId);
+ }
}
mTaskFpsCallbackController.registerListener(taskId, callback);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b43e334d6e1a..3a2a1ea419d4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5424,7 +5424,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// change then delay the position update until it has redrawn to avoid any flickers.
final boolean isLetterboxedAndRelaunching = activityRecord != null
&& activityRecord.areBoundsLetterboxed()
- && activityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+ && activityRecord.mAppCompatController.getOrientationOverrides()
.getIsRelaunchingAfterRequestedOrientationChanged();
if (surfaceResizedWithoutMoveAnimation || isLetterboxedAndRelaunching) {
applyWithNextDraw(mSetSurfacePositionConsumer);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 379e312e58c0..911c686c711f 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -343,7 +343,7 @@ public:
void setPointerDisplayId(ui::LogicalDisplayId displayId);
int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
- void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
+ void setMouseScalingEnabled(ui::LogicalDisplayId displayId, bool enabled);
void setMouseReverseVerticalScrollingEnabled(bool enabled);
void setMouseScrollingAccelerationEnabled(bool enabled);
void setMouseScrollingSpeed(int32_t speed);
@@ -474,8 +474,8 @@ private:
// Pointer speed.
int32_t pointerSpeed{0};
- // Displays on which its associated mice will have pointer acceleration disabled.
- std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{};
+ // Displays on which its associated mice will have all scaling disabled.
+ std::set<ui::LogicalDisplayId> displaysWithMouseScalingDisabled{};
// True if pointer gestures are enabled.
bool pointerGesturesEnabled{true};
@@ -603,9 +603,8 @@ void NativeInputManager::dump(std::string& dump) {
dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
- dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
- dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled,
- streamableToString)
+ dump += StringPrintf(INDENT "Display with Mouse Scaling Disabled: %s\n",
+ dumpSet(mLocked.displaysWithMouseScalingDisabled, streamableToString)
.c_str());
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
@@ -834,13 +833,11 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
std::scoped_lock _l(mLock);
outConfig->mousePointerSpeed = mLocked.pointerSpeed;
- outConfig->displaysWithMousePointerAccelerationDisabled =
- mLocked.displaysWithMousePointerAccelerationDisabled;
+ outConfig->displaysWithMouseScalingDisabled = mLocked.displaysWithMouseScalingDisabled;
outConfig->pointerVelocityControlParameters.scale =
exp2f(mLocked.pointerSpeed * POINTER_SPEED_EXPONENT);
outConfig->pointerVelocityControlParameters.acceleration =
- mLocked.displaysWithMousePointerAccelerationDisabled.count(
- mLocked.pointerDisplayId) == 0
+ mLocked.displaysWithMouseScalingDisabled.count(mLocked.pointerDisplayId) == 0
? android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION
: 1;
outConfig->wheelVelocityControlParameters.acceleration =
@@ -1519,23 +1516,21 @@ void NativeInputManager::setPointerSpeed(int32_t speed) {
InputReaderConfiguration::Change::POINTER_SPEED);
}
-void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId,
- bool enabled) {
+void NativeInputManager::setMouseScalingEnabled(ui::LogicalDisplayId displayId, bool enabled) {
{ // acquire lock
std::scoped_lock _l(mLock);
- const bool oldEnabled =
- mLocked.displaysWithMousePointerAccelerationDisabled.count(displayId) == 0;
+ const bool oldEnabled = mLocked.displaysWithMouseScalingDisabled.count(displayId) == 0;
if (oldEnabled == enabled) {
return;
}
- ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled),
+ ALOGI("Setting mouse pointer scaling to %s on display %s", toString(enabled),
displayId.toString().c_str());
if (enabled) {
- mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
+ mLocked.displaysWithMouseScalingDisabled.erase(displayId);
} else {
- mLocked.displaysWithMousePointerAccelerationDisabled.emplace(displayId);
+ mLocked.displaysWithMouseScalingDisabled.emplace(displayId);
}
} // release lock
@@ -2589,11 +2584,11 @@ static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed
im->setPointerSpeed(speed);
}
-static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
- jint displayId, jboolean enabled) {
+static void nativeSetMouseScalingEnabled(JNIEnv* env, jobject nativeImplObj, jint displayId,
+ jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled);
+ im->setMouseScalingEnabled(ui::LogicalDisplayId{displayId}, enabled);
}
static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -3340,8 +3335,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouchOnDisplay},
{"getMousePointerSpeed", "()I", (void*)nativeGetMousePointerSpeed},
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
- {"setMousePointerAccelerationEnabled", "(IZ)V",
- (void*)nativeSetMousePointerAccelerationEnabled},
+ {"setMouseScalingEnabled", "(IZ)V", (void*)nativeSetMouseScalingEnabled},
{"setMouseReverseVerticalScrollingEnabled", "(Z)V",
(void*)nativeSetMouseReverseVerticalScrollingEnabled},
{"setMouseScrollingAccelerationEnabled", "(Z)V",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2627895b8c63..e69a7414dd76 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21907,7 +21907,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
accountToMigrate,
sourceUser,
targetUser,
- /* callback= */ null, /* handler= */ null)
+ /* handler= */ null, /* callback= */ null)
.getResult(60 * 3, TimeUnit.SECONDS);
if (copySucceeded) {
logCopyAccountStatus(COPY_ACCOUNT_SUCCEEDED, callerPackage);
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 a9ad435762ad..02e5470e8673 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -415,7 +415,6 @@ public class DisplayManagerServiceTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
mLocalServiceKeeperRule.overrideLocalService(
InputManagerInternal.class, mMockInputManagerInternal);
@@ -2797,30 +2796,7 @@ public class DisplayManagerServiceTest {
}
@Test
- public void testConnectExternalDisplay_withoutDisplayManagement_shouldAddDisplay() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
- manageDisplaysPermission(/* granted= */ true);
- DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
- DisplayManagerService.BinderService bs = displayManager.new BinderService();
- LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
- FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
- bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
- callback.expectsEvent(EVENT_DISPLAY_ADDED);
-
- FakeDisplayDevice displayDevice =
- createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
- callback.waitForExpectedEvent();
-
- LogicalDisplay display =
- logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
- assertThat(display.isEnabledLocked()).isTrue();
- assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED);
-
- }
-
- @Test
- public void testConnectExternalDisplay_withDisplayManagement_shouldDisableDisplay() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testConnectExternalDisplay_shouldDisableDisplay() {
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -2849,9 +2825,8 @@ public class DisplayManagerServiceTest {
}
@Test
- public void testConnectExternalDisplay_withDisplayManagementAndSysprop_shouldEnableDisplay() {
+ public void testConnectExternalDisplay_withSysprop_shouldEnableDisplay() {
Assume.assumeTrue(Build.IS_ENG || Build.IS_USERDEBUG);
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
doAnswer((Answer<Boolean>) invocationOnMock -> true)
.when(() -> SystemProperties.getBoolean(ENABLE_ON_CONNECT, false));
manageDisplaysPermission(/* granted= */ true);
@@ -2883,8 +2858,7 @@ public class DisplayManagerServiceTest {
}
@Test
- public void testConnectExternalDisplay_withDisplayManagement_allowsEnableAndDisableDisplay() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testConnectExternalDisplay_allowsEnableAndDisableDisplay() {
when(mMockFlags.isApplyDisplayChangedDuringDisplayAddedEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
LocalServices.addService(WindowManagerPolicy.class, mMockedWindowManagerPolicy);
@@ -2955,8 +2929,7 @@ public class DisplayManagerServiceTest {
}
@Test
- public void testConnectInternalDisplay_withDisplayManagement_shouldConnectAndAddDisplay() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testConnectInternalDisplay_shouldConnectAndAddDisplay() {
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
@@ -3011,7 +2984,7 @@ public class DisplayManagerServiceTest {
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
- bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+ bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
callback.expectsEvent(EVENT_DISPLAY_ADDED);
FakeDisplayDevice displayDevice =
@@ -3032,8 +3005,7 @@ public class DisplayManagerServiceTest {
}
@Test
- public void testEnableExternalDisplay_withDisplayManagement_shouldSignalDisplayAdded() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testEnableExternalDisplay_shouldSignalDisplayAdded() {
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -3062,8 +3034,7 @@ public class DisplayManagerServiceTest {
}
@Test
- public void testEnableExternalDisplay_withoutPermission_shouldThrowException() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testEnableExternalDisplay_shouldThrowException() {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3087,8 +3058,7 @@ public class DisplayManagerServiceTest {
}
@Test
- public void testEnableInternalDisplay_withManageDisplays_shouldSignalAdded() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testEnableInternalDisplay_shouldSignalAdded() {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3115,8 +3085,7 @@ public class DisplayManagerServiceTest {
}
@Test
- public void testDisableInternalDisplay_withDisplayManagement_shouldSignalRemove() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testDisableInternalDisplay_shouldSignalRemove() {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3140,7 +3109,6 @@ public class DisplayManagerServiceTest {
@Test
public void testDisableExternalDisplay_shouldSignalDisplayRemoved() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3181,7 +3149,6 @@ public class DisplayManagerServiceTest {
@Test
public void testDisableExternalDisplay_withoutPermission_shouldThrowException() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3207,7 +3174,6 @@ public class DisplayManagerServiceTest {
@Test
public void testRemoveExternalDisplay_whenDisabled_shouldSignalDisconnected() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -3244,7 +3210,6 @@ public class DisplayManagerServiceTest {
@Test
public void testRegisterCallback_withoutPermission_shouldThrow() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
@@ -3255,7 +3220,6 @@ public class DisplayManagerServiceTest {
@Test
public void testRemoveExternalDisplay_whenEnabled_shouldSignalRemovedAndDisconnected() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -3288,7 +3252,6 @@ public class DisplayManagerServiceTest {
@Test
public void testRemoveInternalDisplay_whenEnabled_shouldSignalRemovedAndDisconnected() {
- when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 782262d3f7c9..a48a88cecbc2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -22,7 +22,6 @@ import static android.view.Display.TYPE_INTERNAL;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -47,7 +46,6 @@ import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.testutils.TestHandler;
-import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import org.junit.Before;
@@ -124,7 +122,6 @@ public class ExternalDisplayPolicyTest {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mHandler = new TestHandler(/*callback=*/ null);
- when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(true);
when(mMockedInjector.getFlags()).thenReturn(mMockedFlags);
when(mMockedInjector.getLogicalDisplayMapper()).thenReturn(mMockedLogicalDisplayMapper);
@@ -173,16 +170,6 @@ public class ExternalDisplayPolicyTest {
}
@Test
- public void testTryEnableExternalDisplay_featureDisabled(@TestParameter final boolean enable) {
- when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
- mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay, enable);
- mHandler.flush();
- verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
- verify(mMockedDisplayNotificationManager, never())
- .onHighTemperatureExternalDisplayNotAllowed();
- }
-
- @Test
public void testTryDisableExternalDisplay_criticalThermalCondition() throws RemoteException {
// Disallow external displays due to thermals.
setTemperature(registerThermalListener(), List.of(CRITICAL_TEMPERATURE));
@@ -278,21 +265,6 @@ public class ExternalDisplayPolicyTest {
}
@Test
- public void testNoThermalListenerRegistered_featureDisabled(
- @TestParameter final boolean isConnectedDisplayManagementEnabled,
- @TestParameter final boolean isErrorHandlingEnabled) throws RemoteException {
- assumeFalse(isConnectedDisplayManagementEnabled && isErrorHandlingEnabled);
- when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(
- isConnectedDisplayManagementEnabled);
- when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(
- isErrorHandlingEnabled);
-
- mExternalDisplayPolicy.onBootCompleted();
- verify(mMockedThermalService, never()).registerThermalEventListenerWithType(
- any(), anyInt());
- }
-
- @Test
public void testOnCriticalTemperature_disallowAndAllowExternalDisplay() throws RemoteException {
final var thermalListener = registerThermalListener();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 0dbb6ba58b3c..7d3cd8a8a9ae 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -222,7 +222,6 @@ public class LogicalDisplayMapperTest {
when(mSyntheticModeManagerMock.createAppSupportedModes(any(), any(), anyBoolean()))
.thenAnswer(AdditionalAnswers.returnsSecondArg());
- when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(false);
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mFoldSettingProviderMock,
@@ -351,8 +350,7 @@ public class LogicalDisplayMapperTest {
}
@Test
- public void testDisplayDeviceAddAndRemove_withDisplayManagement() {
- when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testDisplayDeviceAddAndRemove() {
DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
@@ -390,8 +388,7 @@ public class LogicalDisplayMapperTest {
}
@Test
- public void testDisplayDisableEnable_withDisplayManagement() {
- when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(true);
+ public void testDisplayDisableEnable() {
DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay displayAdded = add(device);
@@ -1350,9 +1347,14 @@ public class LogicalDisplayMapperTest {
ArgumentCaptor<LogicalDisplay> displayCaptor =
ArgumentCaptor.forClass(LogicalDisplay.class);
verify(mListenerMock).onLogicalDisplayEventLocked(
- displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED));
+ displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_CONNECTED));
+ LogicalDisplay display = displayCaptor.getValue();
+ if (display.isEnabledLocked()) {
+ verify(mListenerMock).onLogicalDisplayEventLocked(
+ eq(display), eq(LOGICAL_DISPLAY_EVENT_ADDED));
+ }
clearInvocations(mListenerMock);
- return displayCaptor.getValue();
+ return display;
}
private void testDisplayDeviceAddAndRemove_NonInternal(int type) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index fa78dfce0a17..dafe4827b2fe 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -220,6 +220,9 @@ public class AccessibilityManagerServiceTest {
@Mock private ProxyManager mProxyManager;
@Mock private StatusBarManagerInternal mStatusBarManagerInternal;
@Mock private DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ private HearingDevicePhoneCallNotificationController
+ mMockHearingDevicePhoneCallNotificationController;
@Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback;
@Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
private IAccessibilityManager mA11yManagerServiceOnDevice;
@@ -289,7 +292,8 @@ public class AccessibilityManagerServiceTest {
mMockMagnificationController,
mInputFilter,
mProxyManager,
- mFakePermissionEnforcer);
+ mFakePermissionEnforcer,
+ mMockHearingDevicePhoneCallNotificationController);
mA11yms.switchUser(mTestableContext.getUserId());
mTestableLooper.processAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
new file mode 100644
index 000000000000..efea21428937
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Application;
+import android.app.Instrumentation;
+import android.app.NotificationManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDevicePort;
+import android.media.AudioManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for the {@link HearingDevicePhoneCallNotificationController}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class HearingDevicePhoneCallNotificationControllerTest {
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ private static final String TEST_ADDRESS = "55:66:77:88:99:AA";
+
+ private final Application mApplication = ApplicationProvider.getApplicationContext();
+ @Spy
+ private final Context mContext = mApplication.getApplicationContext();
+ private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private NotificationManager mNotificationManager;
+ @Mock
+ private AudioManager mAudioManager;
+ private HearingDevicePhoneCallNotificationController mController;
+ private TestCallStateListener mTestCallStateListener;
+
+ @Before
+ public void setUp() {
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager);
+ when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
+
+ mTestCallStateListener = new TestCallStateListener(mContext);
+ mController = new HearingDevicePhoneCallNotificationController(mContext,
+ mTestCallStateListener);
+ mController.startListenForCallState();
+ }
+
+ @Test
+ public void startListenForCallState_callbackNotNull() {
+ Mockito.reset(mTelephonyManager);
+ mController = new HearingDevicePhoneCallNotificationController(mContext);
+ ArgumentCaptor<TelephonyCallback> listenerCaptor = ArgumentCaptor.forClass(
+ TelephonyCallback.class);
+
+ mController.startListenForCallState();
+
+ verify(mTelephonyManager).registerTelephonyCallback(any(Executor.class),
+ listenerCaptor.capture());
+ TelephonyCallback callback = listenerCaptor.getValue();
+ assertThat(callback).isNotNull();
+ }
+
+ @Test
+ public void onCallStateChanged_stateOffHook_hapDevice_showNotification() {
+ AudioDeviceInfo hapDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{hapDeviceInfo});
+ when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo));
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+
+ verify(mNotificationManager).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any());
+ }
+
+ @Test
+ public void onCallStateChanged_stateOffHook_a2dpDevice_noNotification() {
+ AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{a2dpDeviceInfo});
+ when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(a2dpDeviceInfo));
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+
+ verify(mNotificationManager, never()).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any());
+ }
+
+ @Test
+ public void onCallStateChanged_stateOffHookThenIdle_hapDeviceInfo_cancelNotification() {
+ AudioDeviceInfo hapDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{hapDeviceInfo});
+ when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo));
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE);
+
+ verify(mNotificationManager).cancel(
+ eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH));
+ }
+
+ private AudioDeviceInfo createAudioDeviceInfo(String address, int type) {
+ AudioDevicePort audioDevicePort = mock(AudioDevicePort.class);
+ doReturn(type).when(audioDevicePort).type();
+ doReturn(address).when(audioDevicePort).address();
+ doReturn("testDevice").when(audioDevicePort).name();
+
+ return new AudioDeviceInfo(audioDevicePort);
+ }
+
+ /**
+ * For easier testing for CallStateListener, override methods that contain final object.
+ */
+ private static class TestCallStateListener extends
+ HearingDevicePhoneCallNotificationController.CallStateListener {
+
+ TestCallStateListener(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ boolean isHapClientSupported() {
+ return true;
+ }
+
+ @Override
+ boolean isHapClientDevice(BluetoothAdapter bluetoothAdapter, AudioDeviceInfo info) {
+ return TEST_ADDRESS.equals(info.getAddress());
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 32578a7dc10f..bdbb495db841 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -340,8 +340,7 @@ public class VirtualDeviceManagerServiceTest {
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- doNothing().when(mInputManagerInternalMock)
- .setMousePointerAccelerationEnabled(anyBoolean(), anyInt());
+ doNothing().when(mInputManagerInternalMock).setMouseScalingEnabled(anyBoolean(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0d86d4c3fa28..60a4b9a499c1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -402,6 +402,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
+ /**
public void testPushDynamicShortcut() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
@@ -543,6 +544,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_10));
}
+ */
public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
throws InterruptedException {
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/ConfigInternalForTests.java b/services/tests/timetests/src/com/android/server/timezonedetector/ConfigInternalForTests.java
new file mode 100644
index 000000000000..47e3dc85f6d0
--- /dev/null
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/ConfigInternalForTests.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+import android.annotation.UserIdInt;
+
+public final class ConfigInternalForTests {
+
+ static final @UserIdInt int USER_ID = 9876;
+
+ static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
+ .setTelephonyDetectionFeatureSupported(true)
+ .setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
+ .setGeoDetectionRunInBackgroundEnabled(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(false)
+ .setAutoDetectionEnabledSetting(false)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(false)
+ .build();
+
+ static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
+ .setTelephonyDetectionFeatureSupported(true)
+ .setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
+ .setGeoDetectionRunInBackgroundEnabled(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(false)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+
+ static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
+ .setTelephonyDetectionFeatureSupported(false)
+ .setGeoDetectionFeatureSupported(false)
+ .setTelephonyFallbackSupported(false)
+ .setGeoDetectionRunInBackgroundEnabled(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionEnabledSetting(false)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(false)
+ .build();
+
+ static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
+ .setTelephonyDetectionFeatureSupported(true)
+ .setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
+ .setGeoDetectionRunInBackgroundEnabled(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionEnabledSetting(false)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(false)
+ .build();
+
+ static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
+ .setTelephonyDetectionFeatureSupported(true)
+ .setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
+ .setGeoDetectionRunInBackgroundEnabled(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(false)
+ .build();
+
+ static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
+ .setTelephonyDetectionFeatureSupported(true)
+ .setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
+ .setGeoDetectionRunInBackgroundEnabled(false)
+ .setEnhancedMetricsCollectionEnabled(false)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+}
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
index fc6afe486187..aeb4d9a19ff0 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
@@ -31,7 +31,7 @@ import java.util.Optional;
/**
* A partially implemented, fake implementation of ServiceConfigAccessor for tests.
*
- * <p>This class has rudamentary support for multiple users, but unlike the real thing, it doesn't
+ * <p>This class has rudimentary support for multiple users, but unlike the real thing, it doesn't
* simulate that some settings are global and shared between users. It also delivers config updates
* synchronously.
*/
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index e52e8b60a61d..47a9b2c47173 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -35,6 +35,12 @@ import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_U
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_AUTO_DETECT_NOT_SUPPORTED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_AUTO_DISABLED_GEO_DISABLED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_AUTO_ENABLED_GEO_DISABLED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_AUTO_ENABLED_GEO_ENABLED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_USER_RESTRICTED_AUTO_ENABLED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.USER_ID;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW;
@@ -68,6 +74,7 @@ import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
import android.service.timezone.TimeZoneProviderStatus;
+import android.util.IndentingPrintWriter;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
@@ -82,6 +89,7 @@ import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
@@ -92,7 +100,6 @@ import java.util.function.Function;
@RunWith(JUnitParamsRunner.class)
public class TimeZoneDetectorStrategyImplTest {
- private static final @UserIdInt int USER_ID = 9876;
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234;
/** A time zone used for initialization that does not occur elsewhere in tests. */
private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
@@ -101,7 +108,7 @@ public class TimeZoneDetectorStrategyImplTest {
// Telephony test cases are ordered so that each successive one is of the same or higher score
// than the previous.
- private static final TelephonyTestCase[] TELEPHONY_TEST_CASES = new TelephonyTestCase[] {
+ private static final TelephonyTestCase[] TELEPHONY_TEST_CASES = new TelephonyTestCase[]{
newTelephonyTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, TELEPHONY_SCORE_LOW),
newTelephonyTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
@@ -118,90 +125,6 @@ public class TimeZoneDetectorStrategyImplTest {
TELEPHONY_SCORE_HIGHEST),
};
- private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
- new ConfigurationInternal.Builder()
- .setUserId(USER_ID)
- .setTelephonyDetectionFeatureSupported(true)
- .setGeoDetectionFeatureSupported(true)
- .setTelephonyFallbackSupported(false)
- .setGeoDetectionRunInBackgroundEnabled(false)
- .setEnhancedMetricsCollectionEnabled(false)
- .setUserConfigAllowed(false)
- .setAutoDetectionEnabledSetting(false)
- .setLocationEnabledSetting(true)
- .setGeoDetectionEnabledSetting(false)
- .build();
-
- private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
- new ConfigurationInternal.Builder()
- .setUserId(USER_ID)
- .setTelephonyDetectionFeatureSupported(true)
- .setGeoDetectionFeatureSupported(true)
- .setTelephonyFallbackSupported(false)
- .setGeoDetectionRunInBackgroundEnabled(false)
- .setEnhancedMetricsCollectionEnabled(false)
- .setUserConfigAllowed(false)
- .setAutoDetectionEnabledSetting(true)
- .setLocationEnabledSetting(true)
- .setGeoDetectionEnabledSetting(true)
- .build();
-
- private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
- new ConfigurationInternal.Builder()
- .setUserId(USER_ID)
- .setTelephonyDetectionFeatureSupported(false)
- .setGeoDetectionFeatureSupported(false)
- .setTelephonyFallbackSupported(false)
- .setGeoDetectionRunInBackgroundEnabled(false)
- .setEnhancedMetricsCollectionEnabled(false)
- .setUserConfigAllowed(true)
- .setAutoDetectionEnabledSetting(false)
- .setLocationEnabledSetting(true)
- .setGeoDetectionEnabledSetting(false)
- .build();
-
- private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
- new ConfigurationInternal.Builder()
- .setUserId(USER_ID)
- .setTelephonyDetectionFeatureSupported(true)
- .setGeoDetectionFeatureSupported(true)
- .setTelephonyFallbackSupported(false)
- .setGeoDetectionRunInBackgroundEnabled(false)
- .setEnhancedMetricsCollectionEnabled(false)
- .setUserConfigAllowed(true)
- .setAutoDetectionEnabledSetting(false)
- .setLocationEnabledSetting(true)
- .setGeoDetectionEnabledSetting(false)
- .build();
-
- private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
- new ConfigurationInternal.Builder()
- .setUserId(USER_ID)
- .setTelephonyDetectionFeatureSupported(true)
- .setGeoDetectionFeatureSupported(true)
- .setTelephonyFallbackSupported(false)
- .setGeoDetectionRunInBackgroundEnabled(false)
- .setEnhancedMetricsCollectionEnabled(false)
- .setUserConfigAllowed(true)
- .setAutoDetectionEnabledSetting(true)
- .setLocationEnabledSetting(true)
- .setGeoDetectionEnabledSetting(false)
- .build();
-
- private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
- new ConfigurationInternal.Builder()
- .setUserId(USER_ID)
- .setTelephonyDetectionFeatureSupported(true)
- .setGeoDetectionFeatureSupported(true)
- .setTelephonyFallbackSupported(false)
- .setGeoDetectionRunInBackgroundEnabled(false)
- .setEnhancedMetricsCollectionEnabled(false)
- .setUserConfigAllowed(true)
- .setAutoDetectionEnabledSetting(true)
- .setLocationEnabledSetting(true)
- .setGeoDetectionEnabledSetting(true)
- .build();
-
private static final TelephonyTimeZoneAlgorithmStatus TELEPHONY_ALGORITHM_RUNNING_STATUS =
new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
@@ -421,7 +344,7 @@ public class TimeZoneDetectorStrategyImplTest {
new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion,
TELEPHONY_SCORE_NONE);
script.verifyLatestQualifiedTelephonySuggestionReceived(
- SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+ SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
.verifyLatestQualifiedTelephonySuggestionReceived(SLOT_INDEX2, null);
assertEquals(expectedSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -629,7 +552,7 @@ public class TimeZoneDetectorStrategyImplTest {
*/
@Test
public void testTelephonySuggestionMultipleSlotIndexSuggestionScoringAndSlotIndexBias() {
- String[] zoneIds = { "Europe/London", "Europe/Paris" };
+ String[] zoneIds = {"Europe/London", "Europe/Paris"};
TelephonyTimeZoneSuggestion emptySlotIndex1Suggestion = createEmptySlotIndex1Suggestion();
TelephonyTimeZoneSuggestion emptySlotIndex2Suggestion = createEmptySlotIndex2Suggestion();
QualifiedTelephonyTimeZoneSuggestion expectedEmptySlotIndex1ScoredSuggestion =
@@ -672,7 +595,7 @@ public class TimeZoneDetectorStrategyImplTest {
// Assert internal service state.
script.verifyLatestQualifiedTelephonySuggestionReceived(
- SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
.verifyLatestQualifiedTelephonySuggestionReceived(
SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
@@ -805,14 +728,14 @@ public class TimeZoneDetectorStrategyImplTest {
boolean bypassUserPolicyChecks = false;
boolean expectedResult = true;
script.simulateManualTimeZoneSuggestion(
- USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult)
+ USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult)
.verifyTimeZoneChangedAndReset(manualSuggestion);
assertEquals(manualSuggestion, mTimeZoneDetectorStrategy.getLatestManualSuggestion());
}
@Test
- @Parameters({ "true,true", "true,false", "false,true", "false,false" })
+ @Parameters({"true,true", "true,false", "false,true", "false,false"})
public void testManualSuggestion_autoTimeEnabled_userRestrictions(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
ConfigurationInternal config =
@@ -834,7 +757,7 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Test
- @Parameters({ "true,true", "true,false", "false,true", "false,false" })
+ @Parameters({"true,true", "true,false", "false,true", "false,false"})
public void testManualSuggestion_autoTimeDisabled_userRestrictions(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
ConfigurationInternal config =
@@ -849,7 +772,7 @@ public class TimeZoneDetectorStrategyImplTest {
ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
boolean expectedResult = userConfigAllowed || bypassUserPolicyChecks;
script.simulateManualTimeZoneSuggestion(
- USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult);
+ USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult);
if (expectedResult) {
script.verifyTimeZoneChangedAndReset(manualSuggestion);
assertEquals(manualSuggestion, mTimeZoneDetectorStrategy.getLatestManualSuggestion());
@@ -1258,7 +1181,6 @@ public class TimeZoneDetectorStrategyImplTest {
script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
-
}
// Demonstrate what happens when geolocation is uncertain when telephony fallback is
@@ -1569,7 +1491,7 @@ public class TimeZoneDetectorStrategyImplTest {
boolean bypassUserPolicyChecks = false;
boolean expectedResult = true;
script.simulateManualTimeZoneSuggestion(
- USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult)
+ USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult)
.verifyTimeZoneChangedAndReset(manualSuggestion);
expectedDeviceTimeZoneId = manualSuggestion.getZoneId();
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
@@ -1880,6 +1802,7 @@ public class TimeZoneDetectorStrategyImplTest {
boolean actualResult = mTimeZoneDetectorStrategy.suggestManualTimeZone(
userId, manualTimeZoneSuggestion, bypassUserPolicyChecks);
assertEquals(expectedResult, actualResult);
+
return this;
}
@@ -2001,4 +1924,34 @@ public class TimeZoneDetectorStrategyImplTest {
return new TelephonyTestCase(matchType, quality, expectedScore);
}
+ static class FakeTimeZoneChangeEventListener implements TimeZoneChangeListener {
+ private final List<TimeZoneChangeEvent> mEvents = new ArrayList<>();
+
+ FakeTimeZoneChangeEventListener() {
+ }
+
+ @Override
+ public void process(TimeZoneChangeEvent event) {
+ mEvents.add(event);
+ }
+
+ public List<TimeZoneChangeEvent> getTimeZoneChangeEvents() {
+ return mEvents;
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ // No-op for tests
+ }
+ }
+
+ private static void assertEmpty(Collection<?> collection) {
+ assertTrue(
+ "Expected empty, but contains (" + collection.size() + ") elements: " + collection,
+ collection.isEmpty());
+ }
+
+ private static void assertNotEmpty(Collection<?> collection) {
+ assertFalse("Expected not empty: " + collection, collection.isEmpty());
+ }
}
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 8e79514c875e..f41805d40b0d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -66,7 +66,6 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No
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.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI;
import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
@@ -164,6 +163,7 @@ import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
import com.android.os.AtomsProto.PackageNotificationPreferences;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
+import com.android.server.uri.UriGrantsManagerInternal;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -179,6 +179,9 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -199,9 +202,6 @@ import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
@@ -239,9 +239,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
private NotificationManager.Policy mTestNotificationPolicy;
- private PreferencesHelper mHelper;
- // fresh object for testing xml reading
- private PreferencesHelper mXmlHelper;
+ private TestPreferencesHelper mHelper;
+ // fresh object for testing xml reading; also TestPreferenceHelper in order to avoid interacting
+ // with real IpcDataCaches
+ private TestPreferencesHelper mXmlHelper;
private AudioAttributes mAudioAttributes;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
@@ -378,10 +379,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
when(mClock.millis()).thenReturn(System.currentTimeMillis());
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
resetZenModeHelper();
@@ -793,7 +794,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_oldXml_migrates() throws Exception {
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
@@ -929,7 +930,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
@@ -988,7 +989,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, /* showReviewPermissionsNotification= */ false, mClock);
@@ -1047,7 +1048,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
@@ -1641,7 +1642,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
serializer.flush();
// simulate load after reboot
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
@@ -1696,7 +1697,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
Duration.ofDays(2).toMillis() + System.currentTimeMillis());
// simulate load after reboot
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
loadByteArrayXml(xml.getBytes(), false, USER_ALL);
@@ -1774,10 +1775,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
when(contentResolver.getResourceId(ANDROID_RES_SOUND_URI)).thenReturn(resId).thenThrow(
new FileNotFoundException("")).thenReturn(resId);
- mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
+ mHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
- mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
@@ -3190,7 +3191,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
public void testCreateChannel_noSoundUriPermission_contentSchemeVerified() {
final Uri sound = Uri.parse(SCHEME_CONTENT + "://media/test/sound/uri");
@@ -3210,7 +3210,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
public void testCreateChannel_noSoundUriPermission_fileSchemaIgnored() {
final Uri sound = Uri.parse(SCHEME_FILE + "://path/sound");
@@ -3229,7 +3228,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
public void testCreateChannel_noSoundUriPermission_resourceSchemaIgnored() {
final Uri sound = Uri.parse(SCHEME_ANDROID_RESOURCE + "://resId/sound");
@@ -6573,4 +6571,223 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.setCanBePromoted(PKG_P, UID_P, false, false);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
}
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateChannelCache_invalidateOnCreationAndChange() {
+ mHelper.resetCacheInvalidation();
+ NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+
+ // new channel should invalidate the cache.
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // when the channel data is updated, should invalidate the cache again after that.
+ mHelper.resetCacheInvalidation();
+ NotificationChannel newChannel = channel.copy();
+ newChannel.setName("new name");
+ newChannel.setImportance(IMPORTANCE_HIGH);
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // also for conversations
+ mHelper.resetCacheInvalidation();
+ String parentId = "id";
+ String convId = "conversation";
+ NotificationChannel conv = new NotificationChannel(
+ String.format(CONVERSATION_CHANNEL_ID_FORMAT, parentId, convId), "conversation",
+ IMPORTANCE_DEFAULT);
+ conv.setConversationId(parentId, convId);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, conv, true, false, UID_N_MR1,
+ false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ mHelper.resetCacheInvalidation();
+ NotificationChannel newConv = conv.copy();
+ newConv.setName("changed");
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newConv, true, UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateChannelCache_invalidateOnDelete() {
+ NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+
+ // ignore any invalidations up until now
+ mHelper.resetCacheInvalidation();
+
+ mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // recreate channel and now permanently delete
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+ mHelper.resetCacheInvalidation();
+ mHelper.permanentlyDeleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id");
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateChannelCache_noInvalidationWhenNoChange() {
+ NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+
+ // ignore any invalidations up until now
+ mHelper.resetCacheInvalidation();
+
+ // newChannel, same as the old channel
+ NotificationChannel newChannel = channel.copy();
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, false, UID_N_MR1,
+ false);
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, UID_N_MR1, false);
+
+ // because there were no effective changes, we should not see any cache invalidations
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+ // deletions of a nonexistent channel also don't change anything
+ mHelper.resetCacheInvalidation();
+ mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "nonexistent", UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateCache_multipleUsersAndPackages() {
+ // Setup: create channels for:
+ // pkg O, user
+ // pkg O, work (same channel ID, different user)
+ // pkg N_MR1, user
+ // pkg N_MR1, user, conversation child of above
+ String p2u1ConvId = String.format(CONVERSATION_CHANNEL_ID_FORMAT, "p2", "conv");
+ NotificationChannel p1u1 = new NotificationChannel("p1", "p1u1", IMPORTANCE_DEFAULT);
+ NotificationChannel p1u2 = new NotificationChannel("p1", "p1u2", IMPORTANCE_DEFAULT);
+ NotificationChannel p2u1 = new NotificationChannel("p2", "p2u1", IMPORTANCE_DEFAULT);
+ NotificationChannel p2u1Conv = new NotificationChannel(p2u1ConvId, "p2u1 conv",
+ IMPORTANCE_DEFAULT);
+ p2u1Conv.setConversationId("p2", "conv");
+
+ mHelper.createNotificationChannel(PKG_O, UID_O, p1u1, true,
+ false, UID_O, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O + UserHandle.PER_USER_RANGE, p1u2, true,
+ false, UID_O + UserHandle.PER_USER_RANGE, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, p2u1, true,
+ false, UID_N_MR1, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, p2u1Conv, true,
+ false, UID_N_MR1, false);
+ mHelper.resetCacheInvalidation();
+
+ // Update to an existent channel, with a change: should invalidate
+ NotificationChannel p1u1New = p1u1.copy();
+ p1u1New.setName("p1u1 new");
+ mHelper.updateNotificationChannel(PKG_O, UID_O, p1u1New, true, UID_O, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // Do it again, but no change for this user
+ mHelper.resetCacheInvalidation();
+ mHelper.updateNotificationChannel(PKG_O, UID_O, p1u1New.copy(), true, UID_O, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+ // Delete conversations, but for a package without those conversations
+ mHelper.resetCacheInvalidation();
+ mHelper.deleteConversations(PKG_O, UID_O, Set.of(p2u1Conv.getConversationId()), UID_O,
+ false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+ // Now delete conversations for the right package
+ mHelper.resetCacheInvalidation();
+ mHelper.deleteConversations(PKG_N_MR1, UID_N_MR1, Set.of(p2u1Conv.getConversationId()),
+ UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateCache_userRemoved() throws Exception {
+ NotificationChannel c1 = new NotificationChannel("id1", "name1", IMPORTANCE_DEFAULT);
+ int uid1 = UserHandle.getUid(1, 1);
+ setUpPackageWithUid("pkg1", uid1);
+ mHelper.createNotificationChannel("pkg1", uid1, c1, true, false, uid1, false);
+ mHelper.resetCacheInvalidation();
+
+ // delete user 1; should invalidate cache
+ mHelper.onUserRemoved(1);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateCache_packagesChanged() {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
+ UID_N_MR1, false);
+
+ // package deleted: expect cache invalidation
+ mHelper.resetCacheInvalidation();
+ mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1},
+ new int[]{UID_N_MR1});
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // re-created: expect cache invalidation again
+ mHelper.resetCacheInvalidation();
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
+ UID_N_MR1, false);
+ mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1},
+ new int[]{UID_N_MR1});
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateCache_flagOff_neverTouchesCache() {
+ // Do a bunch of channel-changing operations.
+ NotificationChannel channel =
+ new NotificationChannel("id", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false,
+ UID_N_MR1, false);
+
+ NotificationChannel copy = channel.copy();
+ copy.setName("name2");
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, copy, true, UID_N_MR1, false);
+ mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", UID_N_MR1, false);
+
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+ }
+
+ // Test version of PreferencesHelper whose only functional difference is that it does not
+ // interact with the real IpcDataCache, and instead tracks whether or not the cache has been
+ // invalidated since creation or the last reset.
+ private static class TestPreferencesHelper extends PreferencesHelper {
+ private boolean mCacheInvalidated = false;
+
+ TestPreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+ ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
+ NotificationChannelLogger notificationChannelLogger,
+ AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
+ UriGrantsManagerInternal ugmInternal,
+ boolean showReviewPermissionsNotification, Clock clock) {
+ super(context, pm, rankingHandler, zenHelper, permHelper, permManager,
+ notificationChannelLogger, appOpsManager, userProfiles, ugmInternal,
+ showReviewPermissionsNotification, clock);
+ }
+
+ @Override
+ protected void invalidateNotificationChannelCache() {
+ mCacheInvalidated = true;
+ }
+
+ boolean hasCacheBeenInvalidated() {
+ return mCacheInvalidated;
+ }
+
+ void resetCacheInvalidation() {
+ mCacheInvalidated = false;
+ }
+ }
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/BasicToPwleSegmentAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/BasicToPwleSegmentAdapterTest.java
new file mode 100644
index 000000000000..09f573cd1ee0
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/BasicToPwleSegmentAdapterTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.BasicPwleSegment;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PwleSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+public class BasicToPwleSegmentAdapterTest {
+
+ private static final float TEST_RESONANT_FREQUENCY = 150;
+ private static final float[] TEST_FREQUENCIES =
+ new float[]{90f, 120f, 150f, 60f, 30f, 210f, 270f, 300f, 240f, 180f};
+ private static final float[] TEST_OUTPUT_ACCELERATIONS =
+ new float[]{1.2f, 1.8f, 2.4f, 0.6f, 0.1f, 2.2f, 1.0f, 0.5f, 1.9f, 3.0f};
+
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_FREQUENCIES,
+ TEST_OUTPUT_ACCELERATIONS);
+
+ private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, null, null);
+
+ private BasicToPwleSegmentAdapter mAdapter;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mAdapter = new BasicToPwleSegmentAdapter();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicPwleSegments_withFeatureFlagDisabled_returnsOriginalSegments() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ // startIntensity, endIntensity, startSharpness, endSharpness, duration
+ new BasicPwleSegment(0.2f, 0.8f, 0.2f, 0.4f, 20),
+ new BasicPwleSegment(0.8f, 0.2f, 0.4f, 0.5f, 100),
+ new BasicPwleSegment(0.2f, 0.65f, 0.5f, 0.5f, 50)));
+ List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+ VibratorInfo vibratorInfo = createVibratorInfo(
+ TEST_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+
+ assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ -1))
+ .isEqualTo(-1);
+ assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 1))
+ .isEqualTo(1);
+
+ assertThat(segments).isEqualTo(originalSegments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicPwleSegments_noPwleCapability_returnsOriginalSegments() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ // startIntensity, endIntensity, startSharpness, endSharpness, duration
+ new BasicPwleSegment(0.2f, 0.8f, 0.2f, 0.4f, 20),
+ new BasicPwleSegment(0.8f, 0.2f, 0.4f, 0.5f, 100),
+ new BasicPwleSegment(0.2f, 0.65f, 0.5f, 0.5f, 50)));
+ List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+ VibratorInfo vibratorInfo = createVibratorInfo(TEST_FREQUENCY_PROFILE);
+
+ assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ -1))
+ .isEqualTo(-1);
+ assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 1))
+ .isEqualTo(1);
+
+ assertThat(segments).isEqualTo(originalSegments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicPwleSegments_invalidFrequencyProfile_returnsOriginalSegments() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ // startIntensity, endIntensity, startSharpness, endSharpness, duration
+ new BasicPwleSegment(0.2f, 0.8f, 0.2f, 0.4f, 20),
+ new BasicPwleSegment(0.8f, 0.2f, 0.4f, 0.5f, 100),
+ new BasicPwleSegment(0.2f, 0.65f, 0.5f, 0.5f, 50)));
+ List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+ VibratorInfo vibratorInfo = createVibratorInfo(
+ EMPTY_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+
+ assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ -1))
+ .isEqualTo(-1);
+ assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 1))
+ .isEqualTo(1);
+
+ assertThat(segments).isEqualTo(originalSegments);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicPwleSegments_withPwleCapability_adaptSegmentsCorrectly() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+ // startIntensity, endIntensity, startSharpness, endSharpness, duration
+ new BasicPwleSegment(0.0f, 1.0f, 0.0f, 1.0f, 100),
+ new BasicPwleSegment(0.0f, 1.0f, 0.0f, 1.0f, 100),
+ new BasicPwleSegment(0.0f, 1.0f, 0.0f, 1.0f, 100)));
+ List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+ // startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration
+ new PwleSegment(0.0f, 1.0f, 30.0f, 300.0f, 100),
+ new PwleSegment(0.0f, 1.0f, 30.0f, 300.0f, 100),
+ new PwleSegment(0.0f, 1.0f, 30.0f, 300.0f, 100));
+ VibratorInfo vibratorInfo = createVibratorInfo(
+ TEST_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+
+ assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 1))
+ .isEqualTo(1);
+
+ assertThat(segments).isEqualTo(expectedSegments);
+ }
+
+ private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyProfile frequencyProfile,
+ int... capabilities) {
+ return new VibratorInfo.Builder(0)
+ .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
+ .setFrequencyProfile(frequencyProfile)
+ .build();
+ }
+}
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 9d4d94bebfd9..85ef466b2477 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -758,6 +758,18 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES)
+ public void testKeyGestureToggleVoiceAccess() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ mPhoneWindowManager.assertVoiceAccess(true);
+
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ mPhoneWindowManager.assertVoiceAccess(false);
+ }
+
+ @Test
public void testKeyGestureToggleDoNotDisturb() {
mPhoneWindowManager.overrideZenMode(Settings.Global.ZEN_MODE_OFF);
Assert.assertTrue(
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6c48ba26a475..4ff3d433632a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -201,6 +201,8 @@ class TestPhoneWindowManager {
private boolean mIsTalkBackEnabled;
private boolean mIsTalkBackShortcutGestureEnabled;
+ private boolean mIsVoiceAccessEnabled;
+
private Intent mBrowserIntent;
private Intent mSmsIntent;
@@ -225,6 +227,18 @@ class TestPhoneWindowManager {
}
}
+ private class TestVoiceAccessShortcutController extends VoiceAccessShortcutController {
+ TestVoiceAccessShortcutController(Context context) {
+ super(context);
+ }
+
+ @Override
+ boolean toggleVoiceAccess(int currentUserId) {
+ mIsVoiceAccessEnabled = !mIsVoiceAccessEnabled;
+ return mIsVoiceAccessEnabled;
+ }
+ }
+
private class TestInjector extends PhoneWindowManager.Injector {
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
super(context, funcs);
@@ -260,6 +274,10 @@ class TestPhoneWindowManager {
return new TestTalkbackShortcutController(mContext);
}
+ VoiceAccessShortcutController getVoiceAccessShortcutController() {
+ return new TestVoiceAccessShortcutController(mContext);
+ }
+
WindowWakeUpPolicy getWindowWakeUpPolicy() {
return mWindowWakeUpPolicy;
}
@@ -1024,6 +1042,11 @@ class TestPhoneWindowManager {
Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
}
+ void assertVoiceAccess(boolean expectEnabled) {
+ mTestLooper.dispatchAll();
+ Assert.assertEquals(expectEnabled, mIsVoiceAccessEnabled);
+ }
+
void assertKeyGestureEventSentToKeyGestureController(int gestureType) {
verify(mInputManagerInternal)
.handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
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 c9cbe0fa08c5..6fad82b26808 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -210,7 +210,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
- return new TestStartingWindowOrganizer(mAtm);
+ return new TestStartingWindowOrganizer(mAtm, mDisplayContent);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 9d191cea8acb..a0727a7af87b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -335,7 +335,7 @@ public class AppCompatOrientationOverridesTest extends WindowTestsBase {
}
private AppCompatOrientationOverrides getTopOrientationOverrides() {
- return activity().top().mAppCompatController.getAppCompatOrientationOverrides();
+ return activity().top().mAppCompatController.getOrientationOverrides();
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index a21ab5de5de2..4faa71451a4d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -601,7 +601,7 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase {
}
private AppCompatOrientationOverrides getTopOrientationOverrides() {
- return activity().top().mAppCompatController.getAppCompatOrientationOverrides();
+ return activity().top().mAppCompatController.getOrientationOverrides();
}
private AppCompatOrientationPolicy getTopAppCompatOrientationPolicy() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
index 463254caa845..50419d46f48f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
@@ -159,8 +159,8 @@ public class AppCompatReachabilityOverridesTest extends WindowTestsBase {
@Override
void onPostActivityCreation(@NonNull ActivityRecord activity) {
super.onPostActivityCreation(activity);
- spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
- activity.mAppCompatController.getAppCompatReachabilityPolicy()
+ spyOn(activity.mAppCompatController.getReachabilityOverrides());
+ activity.mAppCompatController.getReachabilityPolicy()
.setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
}
@@ -196,7 +196,7 @@ public class AppCompatReachabilityOverridesTest extends WindowTestsBase {
@NonNull
private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
- return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+ return activity().top().mAppCompatController.getReachabilityOverrides();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
index ddc4de9cfd8a..09b8bce2c930 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
@@ -246,8 +246,8 @@ public class AppCompatReachabilityPolicyTest extends WindowTestsBase {
@Override
void onPostActivityCreation(@NonNull ActivityRecord activity) {
super.onPostActivityCreation(activity);
- spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
- activity.mAppCompatController.getAppCompatReachabilityPolicy()
+ spyOn(activity.mAppCompatController.getReachabilityOverrides());
+ activity.mAppCompatController.getReachabilityPolicy()
.setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
}
@@ -281,12 +281,12 @@ public class AppCompatReachabilityPolicyTest extends WindowTestsBase {
@NonNull
private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
- return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+ return activity().top().mAppCompatController.getReachabilityOverrides();
}
@NonNull
private AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
- return activity().top().mAppCompatController.getAppCompatReachabilityPolicy();
+ return activity().top().mAppCompatController.getReachabilityPolicy();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index ea925c019b77..4854f0d948b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -88,7 +88,7 @@ public class DisplayPolicyTests extends WindowTestsBase {
}
private WindowState createDreamWindow() {
- final WindowState win = createDreamWindow(null, TYPE_BASE_APPLICATION, "dream");
+ final WindowState win = createDreamWindow("dream", TYPE_BASE_APPLICATION);
final WindowManager.LayoutParams attrs = win.mAttrs;
attrs.width = MATCH_PARENT;
attrs.height = MATCH_PARENT;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 9d9f24cb50f2..96b11a87d8df 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -330,7 +330,7 @@ public class SizeCompatTests extends WindowTestsBase {
if (horizontalReachability) {
final Consumer<Integer> doubleClick =
(Integer x) -> {
- mActivity.mAppCompatController.getAppCompatReachabilityPolicy()
+ mActivity.mAppCompatController.getReachabilityPolicy()
.handleDoubleTap(x, displayHeight / 2);
mActivity.mRootWindowContainer.performSurfacePlacement();
};
@@ -360,7 +360,7 @@ public class SizeCompatTests extends WindowTestsBase {
} else {
final Consumer<Integer> doubleClick =
(Integer y) -> {
- mActivity.mAppCompatController.getAppCompatReachabilityPolicy()
+ mActivity.mAppCompatController.getReachabilityPolicy()
.handleDoubleTap(displayWidth / 2, y);
mActivity.mRootWindowContainer.performSurfacePlacement();
};
@@ -421,7 +421,7 @@ public class SizeCompatTests extends WindowTestsBase {
final Consumer<Integer> doubleClick =
(Integer y) -> {
- activity.mAppCompatController.getAppCompatReachabilityPolicy()
+ activity.mAppCompatController.getReachabilityPolicy()
.handleDoubleTap(dw / 2, y);
activity.mRootWindowContainer.performSurfacePlacement();
};
@@ -834,7 +834,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Change the fixed orientation.
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
assertTrue(mActivity.isRelaunching());
- assertTrue(mActivity.mAppCompatController.getAppCompatOrientationOverrides()
+ assertTrue(mActivity.mAppCompatController.getOrientationOverrides()
.getIsRelaunchingAfterRequestedOrientationChanged());
assertFitted();
@@ -3427,7 +3427,7 @@ public class SizeCompatTests extends WindowTestsBase {
setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ false);
final AppCompatReachabilityOverrides reachabilityOverrides =
- mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+ mActivity.mAppCompatController.getReachabilityOverrides();
assertFalse(reachabilityOverrides.isVerticalReachabilityEnabled());
assertFalse(reachabilityOverrides.isHorizontalReachabilityEnabled());
}
@@ -3451,7 +3451,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Horizontal reachability is disabled because the app is in split screen.
- assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
.isHorizontalReachabilityEnabled());
}
@@ -3475,7 +3475,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Vertical reachability is disabled because the app is in split screen.
- assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
.isVerticalReachabilityEnabled());
}
@@ -3498,7 +3498,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Vertical reachability is disabled because the app does not match parent width
assertNotEquals(mActivity.getScreenResolvedBounds().width(),
mActivity.mDisplayContent.getBounds().width());
- assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
.isVerticalReachabilityEnabled());
}
@@ -3516,7 +3516,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
// Vertical reachability is still enabled as resolved bounds is not empty
- assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
.isVerticalReachabilityEnabled());
}
@@ -3533,7 +3533,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
// Horizontal reachability is still enabled as resolved bounds is not empty
- assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
.isHorizontalReachabilityEnabled());
}
@@ -3548,7 +3548,7 @@ public class SizeCompatTests extends WindowTestsBase {
prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
.isHorizontalReachabilityEnabled());
}
@@ -3563,7 +3563,7 @@ public class SizeCompatTests extends WindowTestsBase {
prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
SCREEN_ORIENTATION_LANDSCAPE);
- assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
.isVerticalReachabilityEnabled());
}
@@ -3585,7 +3585,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Horizontal reachability is disabled because the app does not match parent height
assertNotEquals(mActivity.getScreenResolvedBounds().height(),
mActivity.mDisplayContent.getBounds().height());
- assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
.isHorizontalReachabilityEnabled());
}
@@ -3608,7 +3608,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Horizontal reachability is enabled because the app matches parent height
assertEquals(mActivity.getScreenResolvedBounds().height(),
mActivity.mDisplayContent.getBounds().height());
- assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
.isHorizontalReachabilityEnabled());
}
@@ -3631,7 +3631,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Vertical reachability is enabled because the app matches parent width
assertEquals(mActivity.getScreenResolvedBounds().width(),
mActivity.mDisplayContent.getBounds().width());
- assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
.isVerticalReachabilityEnabled());
}
@@ -4315,7 +4315,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Make sure app doesn't jump to top (default tabletop position) when unfolding.
assertEquals(1.0f, mActivity.mAppCompatController
- .getAppCompatReachabilityOverrides().getVerticalPositionMultiplier(mActivity
+ .getReachabilityOverrides().getVerticalPositionMultiplier(mActivity
.getParent().getConfiguration()), 0);
// Simulate display fully open after unfolding.
@@ -4323,7 +4323,7 @@ public class SizeCompatTests extends WindowTestsBase {
doReturn(false).when(mActivity.mDisplayContent).inTransition();
assertEquals(1.0f, mActivity.mAppCompatController
- .getAppCompatReachabilityOverrides().getVerticalPositionMultiplier(mActivity
+ .getReachabilityOverrides().getVerticalPositionMultiplier(mActivity
.getParent().getConfiguration()), 0);
}
@@ -5028,7 +5028,7 @@ public class SizeCompatTests extends WindowTestsBase {
private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
final AppCompatReachabilityOverrides reachabilityOverrides =
- mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+ mActivity.mAppCompatController.getReachabilityOverrides();
spyOn(reachabilityOverrides);
doReturn(thinLetterboxAllowed).when(reachabilityOverrides)
.allowVerticalReachabilityForThinLetterbox();
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 ce0d91264063..37d2a7511d98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -478,7 +478,7 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
private WindowState createCommonWindow(WindowState parent, int type, String name) {
- final WindowState win = createWindow(parent, type, name);
+ final WindowState win = newWindowBuilder(name, type).setParent(parent).build();
// Prevent common windows from been IME targets.
win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
return win;
@@ -502,7 +502,8 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
WindowState createNavBarWithProvidedInsets(DisplayContent dc) {
- final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, dc, "navbar");
+ final WindowState navbar = newWindowBuilder("navbar", TYPE_NAVIGATION_BAR).setDisplay(
+ dc).build();
final Binder owner = new Binder();
navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
@@ -513,7 +514,8 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
WindowState createStatusBarWithProvidedInsets(DisplayContent dc) {
- final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, dc, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).setDisplay(
+ dc).build();
final Binder owner = new Binder();
statusBar.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars())
@@ -575,92 +577,13 @@ public class WindowTestsBase extends SystemServiceTestsBase {
WindowState createAppWindow(Task task, int type, String name) {
final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent());
task.addChild(activity, 0);
- return createWindow(null, type, activity, name);
+ return newWindowBuilder(name, type).setWindowToken(activity).build();
}
- WindowState createDreamWindow(WindowState parent, int type, String name) {
+ WindowState createDreamWindow(String name, int type) {
final WindowToken token = createWindowToken(
mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM, type);
- return createWindow(parent, type, token, name);
- }
-
- // TODO: Move these calls to a builder?
- WindowState createWindow(WindowState parent, int type, String name) {
- return (parent == null)
- ? createWindow(parent, type, mDisplayContent, name)
- : createWindow(parent, type, parent.mToken, name);
- }
-
- WindowState createWindow(WindowState parent, int type, String name, int ownerId) {
- return (parent == null)
- ? createWindow(parent, type, mDisplayContent, name, ownerId)
- : createWindow(parent, type, parent.mToken, name, ownerId);
- }
-
- WindowState createWindow(WindowState parent, int windowingMode, int activityType,
- int type, DisplayContent dc, String name) {
- final WindowToken token = createWindowToken(dc, windowingMode, activityType, type);
- return createWindow(parent, type, token, name);
- }
-
- WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
- return createWindow(
- parent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type, dc, name);
- }
-
- WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
- int ownerId) {
- final WindowToken token = createWindowToken(
- dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
- return createWindow(parent, type, token, name, ownerId);
- }
-
- WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
- boolean ownerCanAddInternalSystemWindow) {
- final WindowToken token = createWindowToken(
- dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
- return createWindow(parent, type, token, name, 0 /* ownerId */,
- ownerCanAddInternalSystemWindow);
- }
-
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
- return createWindow(parent, type, token, name, 0 /* ownerId */,
- false /* ownerCanAddInternalSystemWindow */);
- }
-
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
- int ownerId) {
- return createWindow(parent, type, token, name, ownerId,
- false /* ownerCanAddInternalSystemWindow */);
- }
-
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
- int ownerId, boolean ownerCanAddInternalSystemWindow) {
- return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
- mIWindow);
- }
-
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
- int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) {
- return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
- ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow);
- }
-
- static WindowState createWindow(WindowState parent, int type, WindowToken token,
- String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow,
- WindowManagerService service, Session session, IWindow iWindow) {
- SystemServicesTestRule.checkHoldsLock(service.mGlobalLock);
-
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
- attrs.setTitle(name);
- attrs.packageName = "test";
-
- final WindowState w = new WindowState(service, session, iWindow, token, parent,
- OP_NONE, attrs, VISIBLE, ownerId, userId, ownerCanAddInternalSystemWindow);
- // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
- // adding it to the token...
- token.addWindow(w);
- return w;
+ return newWindowBuilder(name, type).setWindowToken(token).build();
}
static void makeWindowVisible(WindowState... windows) {
@@ -1920,11 +1843,14 @@ public class WindowTestsBase extends SystemServiceTestsBase {
private final WindowManagerService mWMService;
private final SparseArray<IBinder> mTaskAppMap = new SparseArray<>();
private final HashMap<IBinder, WindowState> mAppWindowMap = new HashMap<>();
+ private final DisplayContent mDisplayContent;
- TestStartingWindowOrganizer(ActivityTaskManagerService service) {
+ TestStartingWindowOrganizer(ActivityTaskManagerService service,
+ DisplayContent displayContent) {
mAtm = service;
mWMService = mAtm.mWindowManager;
mAtm.mTaskOrganizerController.registerTaskOrganizer(this);
+ mDisplayContent = displayContent;
}
@Override
@@ -1933,10 +1859,11 @@ public class WindowTestsBase extends SystemServiceTestsBase {
final ActivityRecord activity = ActivityRecord.forTokenLocked(info.appToken);
IWindow iWindow = mock(IWindow.class);
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
- final WindowState window = WindowTestsBase.createWindow(null,
- TYPE_APPLICATION_STARTING, activity,
- "Starting window", 0 /* ownerId */, 0 /* userId*/,
- false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow);
+ // WindowToken is already passed, windowTokenCreator is not needed here.
+ final WindowState window = new WindowTestsBase.WindowStateBuilder("Starting window",
+ TYPE_APPLICATION_STARTING, mWMService, mDisplayContent, iWindow,
+ (unused) -> createTestSession(mAtm),
+ null /* windowTokenCreator */).setWindowToken(activity).build();
activity.mStartingWindow = window;
mAppWindowMap.put(info.appToken, window);
mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 7082f0028a5e..e65e4b05ef98 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -29,6 +29,7 @@ import android.annotation.SuppressAutoDoc;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1886,6 +1887,34 @@ public class TelecomManager {
}
/**
+ * This test API determines the foreground service delegation state for a VoIP app that adds
+ * calls via {@link TelecomManager#addCall(CallAttributes, Executor, OutcomeReceiver,
+ * CallControlCallback, CallEventCallback)}. Foreground Service Delegation allows applications
+ * to operate in the background starting in Android 14 and is granted by Telecom via a request
+ * to the ActivityManager.
+ *
+ * @param handle of the voip app that is being checked
+ * @return true if the app has foreground service delegation. Otherwise, false.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VOIP_CALL_MONITOR_REFACTOR)
+ @TestApi
+ public boolean hasForegroundServiceDelegation(@Nullable PhoneAccountHandle handle) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ return service.hasForegroundServiceDelegation(handle, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG,
+ "RemoteException calling ITelecomService#hasForegroundServiceDelegation.",
+ e);
+ }
+ }
+ return false;
+ }
+
+ /**
* Return the line 1 phone number for given phone account.
*
* <p>Requires Permission:
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index c85374e0b660..b32379ae4b1e 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -409,4 +409,10 @@ interface ITelecomService {
*/
void addCall(in CallAttributes callAttributes, in ICallEventCallback callback, String callId,
String callingPackage);
+
+ /**
+ * @see TelecomServiceImpl#hasForegroundServiceDelegation
+ */
+ boolean hasForegroundServiceDelegation(in PhoneAccountHandle phoneAccountHandle,
+ String callingPackage);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 63a12816f783..b7b209b78300 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -3690,8 +3690,8 @@ public final class SatelliteManager {
* @param list The list of provisioned satellite subscriber infos.
* @param executor The executor on which the callback will be called.
* @param callback The callback object to which the result will be delivered.
- * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
- * will return {@code true}.
+ * If the request is successful, {@link OutcomeReceiver#onResult}
+ * will be called.
* If the request is not successful,
* {@link OutcomeReceiver#onError(Throwable)} will return an error with
* a SatelliteException.
@@ -3704,7 +3704,7 @@ public final class SatelliteManager {
@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void provisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ @NonNull OutcomeReceiver<Void, SatelliteException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -3718,8 +3718,8 @@ public final class SatelliteManager {
if (resultData.containsKey(KEY_PROVISION_SATELLITE_TOKENS)) {
boolean isUpdated =
resultData.getBoolean(KEY_PROVISION_SATELLITE_TOKENS);
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onResult(isUpdated)));
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onResult(null)));
} else {
loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
@@ -3751,8 +3751,8 @@ public final class SatelliteManager {
* @param list The list of deprovisioned satellite subscriber infos.
* @param executor The executor on which the callback will be called.
* @param callback The callback object to which the result will be delivered.
- * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
- * will return {@code true}.
+ * If the request is successful, {@link OutcomeReceiver#onResult}
+ * will be called.
* If the request is not successful,
* {@link OutcomeReceiver#onError(Throwable)} will return an error with
* a SatelliteException.
@@ -3765,7 +3765,7 @@ public final class SatelliteManager {
@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ @NonNull OutcomeReceiver<Void, SatelliteException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -3780,7 +3780,7 @@ public final class SatelliteManager {
boolean isUpdated =
resultData.getBoolean(KEY_DEPROVISION_SATELLITE_TOKENS);
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onResult(isUpdated)));
+ callback.onResult(null)));
} else {
loge("KEY_DEPROVISION_SATELLITE_TOKENS does not exist.");
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 08b5f38a4655..75bd5d157bb2 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.activityembedding.open
import android.graphics.Rect
-import android.platform.test.annotations.Presubmit
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -68,13 +67,21 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest
}
}
- @Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {}
+ @Ignore("Not applicable to this CUJ.")
+ @Test
+ override fun navBarWindowIsVisibleAtStartAndEnd() {}
- @FlakyTest(bugId = 291575593) override fun entireScreenCovered() {}
+ @FlakyTest(bugId = 291575593)
+ @Test
+ override fun entireScreenCovered() {}
- @Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {}
+ @Ignore("Not applicable to this CUJ.")
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() {}
- @Ignore("Not applicable to this CUJ.") override fun statusBarLayerPositionAtStartAndEnd() {}
+ @Ignore("Not applicable to this CUJ.")
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {}
/** Transition begins with a split. */
@FlakyTest(bugId = 286952194)
@@ -122,7 +129,6 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest
/** Always expand activity is on top of the split. */
@FlakyTest(bugId = 286952194)
- @Presubmit
@Test
fun endsWithAlwaysExpandActivityOnTop() {
flicker.assertWmEnd {
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 0ca8f37b239b..e41364595648 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -176,12 +176,15 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa
}
@Ignore("Not applicable to this CUJ.")
+ @Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
@FlakyTest(bugId = 342596801)
+ @Test
override fun entireScreenCovered() = super.entireScreenCovered()
@FlakyTest(bugId = 342596801)
+ @Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index b8f11dcf8970..ad083fa428a9 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.ime
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
@@ -81,7 +80,6 @@ class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: LegacyF
}
@FlakyTest(bugId = 290767483)
- @Postsubmit
@Test
fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() {
val layerTrace = flicker.reader.readLayersTrace() ?: error("Unable to read layers trace")
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 4d7085feb98f..d35c9008e8cb 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -830,6 +830,18 @@ class KeyGestureControllerTests {
KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
+ TestData(
+ "META + ALT + 'V' -> Toggle Voice Access",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_V
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS,
+ intArrayOf(KeyEvent.KEYCODE_V),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}
@@ -843,6 +855,7 @@ class KeyGestureControllerTests {
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
+ com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
@@ -861,6 +874,7 @@ class KeyGestureControllerTests {
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
+ com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)