summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt4
-rw-r--r--core/java/android/app/ActivityManagerInternal.java9
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/KeyguardManager.java3
-rw-r--r--core/java/android/app/LoadedApk.java4
-rw-r--r--core/java/android/app/Notification.java36
-rw-r--r--core/java/android/app/backup/BackupManagerInternal.java40
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl32
-rw-r--r--core/java/android/companion/CompanionDeviceService.java33
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceInternal.java8
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java5
-rw-r--r--core/java/android/content/Intent.java56
-rw-r--r--core/java/android/content/pm/PackageManager.java2
-rw-r--r--core/java/android/content/res/ApkAssets.java58
-rw-r--r--core/java/android/content/res/ResourceTimer.java56
-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/fingerprint/FingerprintSensorPropertiesInternal.java9
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl5
-rw-r--r--core/java/android/hardware/input/InputGestureData.java121
-rw-r--r--core/java/android/hardware/input/InputManager.java24
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java26
-rw-r--r--core/java/android/os/Debug.java8
-rw-r--r--core/java/android/os/GraphicsEnvironment.java4
-rw-r--r--core/java/android/os/Parcel.java8
-rw-r--r--core/java/android/os/ServiceManager.java2
-rw-r--r--core/java/android/os/ServiceManagerNative.java15
-rw-r--r--core/java/android/provider/Settings.java9
-rw-r--r--core/java/android/security/flags.aconfig10
-rw-r--r--core/java/android/service/dreams/flags.aconfig7
-rw-r--r--core/java/android/view/PointerIcon.java12
-rw-r--r--core/java/android/widget/TextView.java28
-rw-r--r--core/java/android/window/DisplayAreaOrganizer.java8
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java131
-rw-r--r--core/java/com/android/internal/security/VerityUtils.java17
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl3
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java52
-rw-r--r--core/java/com/android/internal/widget/LockscreenCredential.java4
-rw-r--r--core/java/com/android/internal/widget/NotificationExpandButton.java48
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressBar.java631
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressDrawable.java389
-rw-r--r--core/jni/android_content_res_ApkAssets.cpp66
-rw-r--r--core/jni/android_hardware_UsbDeviceConnection.cpp22
-rw-r--r--core/jni/android_os_Debug.cpp12
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--core/res/res/drawable/notification_progress.xml1
-rw-r--r--core/res/res/values-round-watch/dimens.xml2
-rw-r--r--core/res/res/values/attrs.xml22
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/dimens.xml2
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java77
-rw-r--r--core/tests/coretests/src/android/os/OWNERS3
-rw-r--r--core/tests/coretests/src/android/os/ParcelTest.java6
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java603
-rw-r--r--graphics/java/android/graphics/Paint.java54
-rw-r--r--graphics/java/android/graphics/fonts/FontVariationAxis.java17
-rw-r--r--keystore/java/android/security/keystore/KeyStoreManager.java6
-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/animation/SizeChangeAnimation.java288
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOut.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java158
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutDisplayAreaOrganizer.java157
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java2
-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/appzoomout/AppZoomOutControllerTest.java87
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt536
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt62
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt117
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java56
-rw-r--r--libs/androidfw/ApkAssets.cpp9
-rw-r--r--libs/androidfw/AssetsProvider.cpp82
-rw-r--r--libs/androidfw/Idmap.cpp21
-rw-r--r--libs/androidfw/ResourceTypes.cpp78
-rw-r--r--libs/androidfw/Util.cpp25
-rw-r--r--libs/androidfw/ZipUtils.cpp34
-rw-r--r--libs/androidfw/include/androidfw/ApkAssets.h2
-rw-r--r--libs/androidfw/include/androidfw/AssetsProvider.h25
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h32
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h48
-rw-r--r--libs/androidfw/include/androidfw/misc.h6
-rw-r--r--libs/androidfw/misc.cpp69
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp27
-rw-r--r--libs/appfunctions/api/current.txt3
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java44
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp8
-rw-r--r--libs/hwui/hwui/Typeface.cpp83
-rw-r--r--libs/hwui/hwui/Typeface.h30
-rw-r--r--libs/hwui/jni/Paint.cpp5
-rw-r--r--libs/hwui/jni/Typeface.cpp40
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp2
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp4
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp7
-rw-r--r--libs/hwui/tests/unit/TypefaceTests.cpp252
-rw-r--r--media/TEST_MAPPING5
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/MediaRoute2Info.java3
-rw-r--r--media/java/android/media/MediaRouter2.java4
-rw-r--r--media/java/android/media/MediaRouter2Manager.java4
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig10
-rw-r--r--mime/Android.bp37
-rw-r--r--mime/jarjar-rules-alt.txt1
-rw-r--r--mime/jarjar-rules.txt2
-rw-r--r--native/android/tests/system_health/OWNERS1
-rw-r--r--packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java16
-rw-r--r--packages/PrintSpooler/Android.bp15
-rw-r--r--packages/PrintSpooler/flags/flags.aconfig9
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java7
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt1
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt1
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt (renamed from packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt)4
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt6
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt20
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java21
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java58
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java18
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java20
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java39
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java50
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java27
-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/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig25
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt125
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt33
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt13
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt52
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt100
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt41
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt126
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt)103
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt)0
-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/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt13
-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/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/InternetTileNewImplTest.kt45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt46
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt92
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt97
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java2
-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/phone/BiometricsUnlockControllerTest.java26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt)30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt694
-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.kt31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt181
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt8
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt4
-rw-r--r--packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt12
-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_ringer_button.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java34
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt19
-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/viewmodel/ShortcutCustomizationViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt8
-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/qs/tiles/viewmodel/QSTileViewModelAdapter.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt32
-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/NotificationLockscreenUserManagerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipViewBinding.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt208
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModel.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/model/TopPinnedState.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt6
-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/ChannelEditorListView.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java12
-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/row/NotificationRowContentBinderImpl.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java18
-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/BiometricUnlockController.java2
-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/phone/ongoingcall/OngoingCallController.kt134
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt53
-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.kt60
-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.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ui/StatusBarUiLayerModule.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt109
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java14
-rw-r--r--packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt3
-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/kosmos/GeneralKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelKosmos.kt35
-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.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/VolumeDialogKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponentKosmos.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/VolumeDialogRingerViewBinderKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractorKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinderKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModelKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt84
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinderKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinderKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModelKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModelKosmos.kt37
-rw-r--r--packages/Vcn/service-b/Android.bp4
-rw-r--r--packages/Vcn/service-b/service-utils/android/util/LocalLog.java148
-rw-r--r--packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java142
-rw-r--r--packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt3
-rw-r--r--ravenwood/Android.bp35
-rw-r--r--ravenwood/CleanSpec.mk45
-rwxr-xr-xravenwood/tools/hoststubgen/scripts/dump-jar2
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt8
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt373
-rwxr-xr-xravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh7
-rwxr-xr-xravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java6
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java69
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java2
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java79
-rw-r--r--services/core/java/com/android/server/am/UserController.java85
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java21
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java25
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java10
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java74
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java5
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java176
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudger.java11
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java12
-rw-r--r--services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java5
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java4
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java2
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java7
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java8
-rw-r--r--services/core/java/com/android/server/media/TEST_MAPPING5
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java68
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java34
-rw-r--r--services/core/java/com/android/server/notification/NotificationDelegate.java5
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java34
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java81
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java4
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java75
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java81
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java16
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java3
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java4
-rw-r--r--services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java1
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java13
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java33
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java4
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java2
-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.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaPolicy.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java56
-rw-r--r--services/core/java/com/android/server/wm/DragState.java24
-rw-r--r--services/core/java/com/android/server/wm/PersisterQueue.java28
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java8
-rw-r--r--services/core/java/com/android/server/wm/StartingData.java7
-rw-r--r--services/core/java/com/android/server/wm/Task.java4
-rw-r--r--services/core/java/com/android/server/wm/Transition.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java17
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java22
-rw-r--r--services/java/com/android/server/SystemServer.java20
-rw-r--r--services/print/java/com/android/server/print/flags.aconfig2
-rw-r--r--services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java18
-rw-r--r--services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java28
-rw-r--r--services/tests/powerstatstests/Android.bp3
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTraceTest.java105
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java16
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java128
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java79
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java4
-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/DisplayContentTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java164
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java103
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java7
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java29
-rw-r--r--telecomm/java/com/android/internal/telecom/ITelecomService.aidl6
-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/AndroidManifest.xml2
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt27
-rw-r--r--tests/Input/src/com/android/test/input/CaptureEventActivity.kt85
-rw-r--r--tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt1
-rw-r--r--tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java25
-rw-r--r--tools/aapt2/cmd/Command.cpp24
-rw-r--r--tools/aapt2/cmd/Command_test.cpp18
-rw-r--r--tools/aapt2/cmd/Convert.cpp3
-rw-r--r--tools/aapt2/cmd/Convert.h9
-rw-r--r--tools/aapt2/cmd/Link.cpp3
-rw-r--r--tools/aapt2/cmd/Link.h9
-rw-r--r--tools/aapt2/cmd/Optimize.cpp3
-rw-r--r--tools/aapt2/cmd/Optimize.h11
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp2
-rw-r--r--tools/aapt2/format/binary/TableFlattener.h5
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp12
-rw-r--r--tools/aapt2/integration-tests/DumpTest/components_full_proto.txt2
-rw-r--r--tools/aapt2/readme.md6
-rw-r--r--tools/aapt2/util/Util.cpp2
471 files changed, 11730 insertions, 4733 deletions
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/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 999db18a1229..6151b8e2ef0a 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -142,6 +142,15 @@ public abstract class ActivityManagerInternal {
String processName, String abiOverride, int uid, Runnable crashHandler);
/**
+ * Called when a user is being deleted. This can happen during normal device usage
+ * or just at startup, when partially removed users are purged. Any state persisted by the
+ * ActivityManager should be purged now.
+ *
+ * @param userId The user being cleaned up.
+ */
+ public abstract void onUserRemoving(@UserIdInt int userId);
+
+ /**
* Called when a user has been deleted. This can happen during normal device usage
* or just at startup, when partially removed users are purged. Any state persisted by the
* ActivityManager should be purged now.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1f3e6559a695..717a2acb4b4a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -105,7 +105,6 @@ import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.content.res.ResourceTimer;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.loader.ResourcesLoader;
@@ -5254,7 +5253,6 @@ public final class ActivityThread extends ClientTransactionHandler
Resources.dumpHistory(pw, "");
pw.flush();
- ResourceTimer.dumpTimers(info.fd.getFileDescriptor(), "-refresh");
if (info.finishCallback != null) {
info.finishCallback.sendResult(null);
}
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 67f7bee4028e..b5ac4e78c7ad 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -70,7 +70,6 @@ import com.android.internal.widget.VerifyCredentialResponse;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.Charset;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -1064,7 +1063,7 @@ public class KeyguardManager {
Log.e(TAG, "Save lock exception", e);
success = false;
} finally {
- Arrays.fill(password, (byte) 0);
+ LockPatternUtils.zeroize(password);
}
return success;
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 3d85ea6a1fca..ffd235f91e09 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1129,6 +1129,10 @@ public final class LoadedApk {
@UnsupportedAppUsage
public ClassLoader getClassLoader() {
+ ClassLoader ret = mClassLoader;
+ if (ret != null) {
+ return ret;
+ }
synchronized (mLock) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5176aee9051f..c2ce7d511681 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1968,6 +1968,13 @@ public class Notification implements Parcelable
@SystemApi
public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
+ /**
+ * {@link #extras} key to a boolean defining if this action requires special visual
+ * treatment.
+ * @hide
+ */
+ public static final String EXTRA_IS_MAGIC = "android.extra.IS_MAGIC";
+
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Icon mIcon;
@@ -5984,6 +5991,15 @@ public class Notification implements Parcelable
}
setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
+ // Update margins to leave space for the top line (but not for headerless views like
+ // HUNS, which use a different layout that already accounts for that).
+ if (Flags.notificationsRedesignTemplates() && !p.mHeaderless) {
+ int margin = getContentMarginTop(mContext,
+ R.dimen.notification_2025_content_margin_top);
+ contentView.setViewLayoutMargin(R.id.notification_main_column,
+ RemoteViews.MARGIN_TOP, margin, TypedValue.COMPLEX_UNIT_PX);
+ }
+
return contentView;
}
@@ -6207,7 +6223,7 @@ public class Notification implements Parcelable
int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
- // Use different highlighted colors for e.g. unopened groups
+ // Use different highlighted colors for conversations' unread count
if (p.mHighlightExpander) {
pillColor = Colors.flattenAlpha(
getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
@@ -6456,16 +6472,6 @@ public class Notification implements Parcelable
big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
- // Update margins to leave space for the top line (but not for HUNs, which use a
- // different layout that already accounts for that).
- if (Flags.notificationsRedesignTemplates()
- && p.mViewType != StandardTemplateParams.VIEW_TYPE_HEADS_UP) {
- int margin = getContentMarginTop(mContext,
- R.dimen.notification_2025_content_margin_top);
- big.setViewLayoutMargin(R.id.notification_main_column, RemoteViews.MARGIN_TOP,
- margin, TypedValue.COMPLEX_UNIT_PX);
- }
-
boolean validRemoteInput = false;
// In the UI, contextual actions appear separately from the standard actions, so we
@@ -6807,8 +6813,6 @@ public class Notification implements Parcelable
public RemoteViews makeNotificationGroupHeader() {
return makeNotificationHeader(mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
- // Highlight group expander until the group is first opened
- .highlightExpander(Flags.notificationsRedesignTemplates())
.fillTextsFrom(this));
}
@@ -6984,14 +6988,12 @@ public class Notification implements Parcelable
* @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
* a new subtext is created consisting of the content of the
* notification.
- * @param highlightExpander whether the expander should use the highlighted colors
* @hide
*/
- public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext,
- boolean highlightExpander) {
+ public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
- .highlightExpander(highlightExpander)
+ .highlightExpander(false)
.fillTextsFrom(this);
if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) {
p.summaryText(createSummaryText());
diff --git a/core/java/android/app/backup/BackupManagerInternal.java b/core/java/android/app/backup/BackupManagerInternal.java
new file mode 100644
index 000000000000..ceb5ae01f177
--- /dev/null
+++ b/core/java/android/app/backup/BackupManagerInternal.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import android.annotation.UserIdInt;
+import android.os.IBinder;
+
+/**
+ * Local system service interface for {@link com.android.server.backup.BackupManagerService}.
+ *
+ * @hide Only for use within the system server.
+ */
+public interface BackupManagerInternal {
+
+ /**
+ * Notifies the Backup Manager Service that an agent has become available. This
+ * method is only invoked by the Activity Manager.
+ */
+ void agentConnectedForUser(String packageName, @UserIdInt int userId, IBinder agent);
+
+ /**
+ * Notify the Backup Manager Service that an agent has unexpectedly gone away.
+ * This method is only invoked by the Activity Manager.
+ */
+ void agentDisconnectedForUser(String packageName, @UserIdInt int userId);
+}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 041c2a7c09f4..5d01d72c35a0 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -93,38 +93,6 @@ interface IBackupManager {
IBackupObserver observer);
/**
- * Notifies the Backup Manager Service that an agent has become available. This
- * method is only invoked by the Activity Manager.
- *
- * If {@code userId} is different from the calling user id, then the caller must hold the
- * android.permission.INTERACT_ACROSS_USERS_FULL permission.
- *
- * @param userId User id for which an agent has become available.
- */
- void agentConnectedForUser(int userId, String packageName, IBinder agent);
-
- /**
- * {@link android.app.backup.IBackupManager.agentConnected} for the calling user id.
- */
- void agentConnected(String packageName, IBinder agent);
-
- /**
- * Notify the Backup Manager Service that an agent has unexpectedly gone away.
- * This method is only invoked by the Activity Manager.
- *
- * If {@code userId} is different from the calling user id, then the caller must hold the
- * android.permission.INTERACT_ACROSS_USERS_FULL permission.
- *
- * @param userId User id for which an agent has unexpectedly gone away.
- */
- void agentDisconnectedForUser(int userId, String packageName);
-
- /**
- * {@link android.app.backup.IBackupManager.agentDisconnected} for the calling user id.
- */
- void agentDisconnected(String packageName);
-
- /**
* Notify the Backup Manager Service that an application being installed will
* need a data-restore pass. This method is only invoked by the Package Manager.
*
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 316d129bd6b9..971d402569c0 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -62,10 +62,11 @@ import java.util.concurrent.Executor;
*
* <p>
* If the companion application has requested observing device presence (see
- * {@link CompanionDeviceManager#startObservingDevicePresence(String)}) the system will
- * <a href="https://developer.android.com/guide/components/bound-services"> bind the service</a>
- * when it detects the device nearby (for BLE devices) or when the device is connected
- * (for Bluetooth devices).
+ * {@link CompanionDeviceManager#stopObservingDevicePresence(ObservingDevicePresenceRequest)})
+ * the system will <a href="https://developer.android.com/guide/components/bound-services">
+ * bind the service</a> when one of the {@link DevicePresenceEvent#EVENT_BLE_APPEARED},
+ * {@link DevicePresenceEvent#EVENT_BT_CONNECTED},
+ * {@link DevicePresenceEvent#EVENT_SELF_MANAGED_APPEARED} event is notified.
*
* <p>
* The system binding {@link CompanionDeviceService} elevates the priority of the process that
@@ -102,15 +103,25 @@ public abstract class CompanionDeviceService extends Service {
/**
* An intent action for a service to be bound whenever this app's companion device(s)
- * are nearby.
+ * are nearby or self-managed device(s) report app appeared.
*
- * <p>The app will be kept alive for as long as the device is nearby or companion app reports
- * appeared.
- * If the app is not running at the time device gets connected, the app will be woken up.</p>
+ * <p>The app will be kept bound by the system when one of the
+ * {@link DevicePresenceEvent#EVENT_BLE_APPEARED},
+ * {@link DevicePresenceEvent#EVENT_BT_CONNECTED},
+ * {@link DevicePresenceEvent#EVENT_SELF_MANAGED_APPEARED} event is notified.
*
- * <p>Shortly after the device goes out of range or the companion app reports disappeared,
- * the service will be unbound, and the app will be eligible for cleanup, unless any other
- * user-visible components are running.</p>
+ * If the app is not running when one of the
+ * {@link DevicePresenceEvent#EVENT_BLE_APPEARED},
+ * {@link DevicePresenceEvent#EVENT_BT_CONNECTED},
+ * {@link DevicePresenceEvent#EVENT_SELF_MANAGED_APPEARED} event is notified, the app will be
+ * kept bound by the system.</p>
+ *
+ * <p>Shortly, the service will be unbound if both
+ * {@link DevicePresenceEvent#EVENT_BLE_DISAPPEARED} and
+ * {@link DevicePresenceEvent#EVENT_BT_DISCONNECTED} are notified, or
+ * {@link DevicePresenceEvent#EVENT_SELF_MANAGED_DISAPPEARED} event is notified.
+ * The app will be eligible for cleanup, unless any other user-visible components are
+ * running.</p>
*
* If running in background is not essential for the devices that this app can manage,
* app should avoid declaring this service.</p>
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 42c74414ecd9..311e24ba6254 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -83,7 +83,6 @@ import java.util.function.IntConsumer;
public class VirtualDeviceInternal {
private final Context mContext;
- private final IVirtualDeviceManager mService;
private final IVirtualDevice mVirtualDevice;
private final Object mActivityListenersLock = new Object();
@GuardedBy("mActivityListenersLock")
@@ -206,7 +205,6 @@ public class VirtualDeviceInternal {
Context context,
int associationId,
VirtualDeviceParams params) throws RemoteException {
- mService = service;
mContext = context.getApplicationContext();
mVirtualDevice = service.createVirtualDevice(
new Binder(),
@@ -217,11 +215,7 @@ public class VirtualDeviceInternal {
mSoundEffectListener);
}
- VirtualDeviceInternal(
- IVirtualDeviceManager service,
- Context context,
- IVirtualDevice virtualDevice) {
- mService = service;
+ VirtualDeviceInternal(Context context, IVirtualDevice virtualDevice) {
mContext = context.getApplicationContext();
mVirtualDevice = virtualDevice;
try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index ed2fd99c55c5..73ea9f0462d5 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -577,9 +577,8 @@ public final class VirtualDeviceManager {
}
/** @hide */
- public VirtualDevice(IVirtualDeviceManager service, Context context,
- IVirtualDevice virtualDevice) {
- mVirtualDeviceInternal = new VirtualDeviceInternal(service, context, virtualDevice);
+ public VirtualDevice(Context context, IVirtualDevice virtualDevice) {
+ mVirtualDeviceInternal = new VirtualDeviceInternal(context, virtualDevice);
}
/**
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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c16582f19c9b..8c7e93a834b7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4649,6 +4649,7 @@ public abstract class PackageManager {
* the Android Keystore backed by an isolated execution environment. The version indicates
* which features are implemented in the isolated execution environment:
* <ul>
+ * <li>400: Inclusion of module information (via tag MODULE_HASH) in the attestation record.
* <li>300: Ability to include a second IMEI in the ID attestation record, see
* {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
* <li>200: Hardware support for Curve 25519 (including both Ed25519 signature generation and
@@ -4682,6 +4683,7 @@ public abstract class PackageManager {
* StrongBox</a>. If this feature has a version, the version number indicates which features are
* implemented in StrongBox:
* <ul>
+ * <li>400: Inclusion of module information (via tag MODULE_HASH) in the attestation record.
* <li>300: Ability to include a second IMEI in the ID attestation record, see
* {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
* <li>200: No new features for StrongBox (the Android Keystore environment backed by an
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index b938aac811fd..075457885586 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -25,7 +25,6 @@ import android.content.res.loader.ResourcesProvider;
import android.ravenwood.annotation.RavenwoodClassLoadHook;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.text.TextUtils;
-import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -51,7 +50,6 @@ import java.util.Objects;
@RavenwoodKeepWholeClass
@RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public final class ApkAssets {
- private static final boolean DEBUG = false;
/**
* The apk assets contains framework resource values specified by the system.
@@ -136,17 +134,6 @@ public final class ApkAssets {
@Nullable
private final AssetsProvider mAssets;
- @NonNull
- private String mName;
-
- private static final int UPTODATE_FALSE = 0;
- private static final int UPTODATE_TRUE = 1;
- private static final int UPTODATE_ALWAYS_TRUE = 2;
-
- // Start with the only value that may change later and would force a native call to
- // double check it.
- private int mPreviousUpToDateResult = UPTODATE_TRUE;
-
/**
* Creates a new ApkAssets instance from the given path on disk.
*
@@ -317,7 +304,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets, path);
+ this(format, flags, assets);
Objects.requireNonNull(path, "path");
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
@@ -326,7 +313,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
throws IOException {
- this(format, flags, assets, friendlyName);
+ this(format, flags, assets);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
@@ -336,7 +323,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets, friendlyName);
+ this(format, flags, assets);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
@@ -344,17 +331,16 @@ public final class ApkAssets {
}
private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
- this(FORMAT_APK, flags, assets, "empty");
+ this(FORMAT_APK, flags, assets);
mNativePtr = nativeLoadEmpty(flags, assets);
mStringBlock = null;
}
private ApkAssets(@FormatType int format, @PropertyFlags int flags,
- @Nullable AssetsProvider assets, @NonNull String name) {
+ @Nullable AssetsProvider assets) {
mFlags = flags;
mAssets = assets;
mIsOverlay = format == FORMAT_IDMAP;
- if (DEBUG) mName = name;
}
@UnsupportedAppUsage
@@ -367,7 +353,7 @@ public final class ApkAssets {
/** @hide */
public @NonNull String getDebugName() {
synchronized (this) {
- return nativeGetDebugName(mNativePtr);
+ return mNativePtr == 0 ? "<destroyed>" : nativeGetDebugName(mNativePtr);
}
}
@@ -435,41 +421,13 @@ public final class ApkAssets {
}
}
- private static double intervalMs(long beginNs, long endNs) {
- return (endNs - beginNs) / 1000000.0;
- }
-
/**
* Returns false if the underlying APK was changed since this ApkAssets was loaded.
*/
public boolean isUpToDate() {
- // This function is performance-critical - it's called multiple times on every Resources
- // object creation, and on few other cache accesses - so it's important to avoid the native
- // call when we know for sure what it will return (which is the case for both ALWAYS_TRUE
- // and FALSE).
- if (mPreviousUpToDateResult != UPTODATE_TRUE) {
- return mPreviousUpToDateResult == UPTODATE_ALWAYS_TRUE;
- }
- final long beforeTs, afterLockTs, afterNativeTs, afterUnlockTs;
- if (DEBUG) beforeTs = System.nanoTime();
- final int res;
synchronized (this) {
- if (DEBUG) afterLockTs = System.nanoTime();
- res = nativeIsUpToDate(mNativePtr);
- if (DEBUG) afterNativeTs = System.nanoTime();
- }
- if (DEBUG) {
- afterUnlockTs = System.nanoTime();
- if (afterUnlockTs - beforeTs >= 10L * 1000000) {
- Log.d("ApkAssets", "isUpToDate(" + mName + ") took "
- + intervalMs(beforeTs, afterUnlockTs)
- + " ms: " + intervalMs(beforeTs, afterLockTs)
- + " / " + intervalMs(afterLockTs, afterNativeTs)
- + " / " + intervalMs(afterNativeTs, afterUnlockTs));
- }
+ return nativeIsUpToDate(mNativePtr);
}
- mPreviousUpToDateResult = res;
- return res != UPTODATE_FALSE;
}
public boolean isSystem() {
@@ -529,7 +487,7 @@ public final class ApkAssets {
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native @NonNull String nativeGetDebugName(long ptr);
private static native long nativeGetStringBlock(long ptr);
- @CriticalNative private static native int nativeIsUpToDate(long ptr);
+ @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java
index 2d1bf4d9d296..d51f64ce8106 100644
--- a/core/java/android/content/res/ResourceTimer.java
+++ b/core/java/android/content/res/ResourceTimer.java
@@ -17,10 +17,13 @@
package android.content.res;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+
import android.app.AppProtoEnums;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemClock;
import android.text.TextUtils;
@@ -30,7 +33,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
-import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -275,40 +277,38 @@ public final class ResourceTimer {
* Update the metrics information and dump it.
* @hide
*/
- public static void dumpTimers(@NonNull FileDescriptor fd, String... args) {
- try (PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd))) {
- pw.println("\nDumping ResourceTimers");
-
- final boolean enabled;
- synchronized (sLock) {
- enabled = sEnabled && sConfig != null;
- }
- if (!enabled) {
+ public static void dumpTimers(@NonNull ParcelFileDescriptor pfd, @Nullable String[] args) {
+ FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
+ PrintWriter pw = new FastPrintWriter(fout);
+ synchronized (sLock) {
+ if (!sEnabled || (sConfig == null)) {
pw.println(" Timers are not enabled in this process");
+ pw.flush();
return;
}
+ }
- // Look for the --refresh switch. If the switch is present, then sTimers is updated.
- // Otherwise, the current value of sTimers is displayed.
- boolean refresh = Arrays.asList(args).contains("-refresh");
-
- synchronized (sLock) {
- update(refresh);
- long runtime = sLastUpdated - sProcessStart;
- pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName());
- for (int i = 0; i < sTimers.length; i++) {
- Timer t = sTimers[i];
- if (t.count != 0) {
- String name = sConfig.timers[i];
- pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
- + "largest=%s\n",
- name, t.count, t.total / t.count, t.mintime, t.maxtime,
- packedString(t.percentile),
- packedString(t.largest));
- }
+ // Look for the --refresh switch. If the switch is present, then sTimers is updated.
+ // Otherwise, the current value of sTimers is displayed.
+ boolean refresh = Arrays.asList(args).contains("-refresh");
+
+ synchronized (sLock) {
+ update(refresh);
+ long runtime = sLastUpdated - sProcessStart;
+ pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName());
+ for (int i = 0; i < sTimers.length; i++) {
+ Timer t = sTimers[i];
+ if (t.count != 0) {
+ String name = sConfig.timers[i];
+ pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
+ + "largest=%s\n",
+ name, t.count, t.total / t.count, t.mintime, t.maxtime,
+ packedString(t.percentile),
+ packedString(t.largest));
}
}
}
+ pw.flush();
}
// Enable (or disabled) the runtime timers. Note that timers are disabled by default. This
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/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index d84d29292ed5..8fbe05c4e9eb 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -121,15 +121,20 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
/**
* Returns if sensor type is ultrasonic Udfps
- * @return true if sensor is ultrasonic Udfps, false otherwise
*/
public boolean isUltrasonicUdfps() {
return sensorType == TYPE_UDFPS_ULTRASONIC;
}
/**
+ * Returns if sensor type is optical Udfps
+ */
+ public boolean isOpticalUdfps() {
+ return sensorType == TYPE_UDFPS_OPTICAL;
+ }
+
+ /**
* Returns if sensor type is side-FPS
- * @return true if sensor is side-fps, false otherwise
*/
public boolean isAnySidefpsType() {
switch (sensorType) {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index ed510e467f82..2bb28a1b6b0b 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -266,6 +266,11 @@ interface IInputManager {
@PermissionManuallyEnforced
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.MANAGE_KEY_GESTURES)")
+ AidlInputGestureData getInputGesture(int userId, in AidlInputGestureData.Trigger trigger);
+
+ @PermissionManuallyEnforced
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
int addCustomInputGesture(int userId, in AidlInputGestureData data);
@PermissionManuallyEnforced
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/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 10224c1be788..cf41e138047a 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1480,6 +1480,30 @@ public final class InputManager {
mGlobal.unregisterKeyGestureEventHandler(handler);
}
+ /**
+ * Find an input gesture mapped to a particular trigger.
+ *
+ * @param trigger to find the input gesture for
+ * @return input gesture mapped to the provided trigger, {@code null} if none found
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+ @UserHandleAware
+ @Nullable
+ public InputGestureData getInputGesture(@NonNull InputGestureData.Trigger trigger) {
+ try {
+ AidlInputGestureData result = mIm.getInputGesture(mContext.getUserId(),
+ trigger.getAidlTrigger());
+ if (result == null) {
+ return null;
+ }
+ return new InputGestureData(result);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** Adds a new custom input gesture
*
* @param inputGestureData gesture data to add as custom gesture
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index cb1e0161441f..4025242fd208 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -43,6 +43,9 @@ public final class KeyGestureEvent {
private static final int LOG_EVENT_UNSPECIFIED =
FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+ // Used as a placeholder to identify if a gesture is reserved for system
+ public static final int KEY_GESTURE_TYPE_SYSTEM_RESERVED = -1;
+
// These values should not change and values should not be re-used as this data is persisted to
// long term storage and must be kept backwards compatible.
public static final int KEY_GESTURE_TYPE_UNSPECIFIED = 0;
@@ -144,6 +147,7 @@ public final class KeyGestureEvent {
public static final int ACTION_GESTURE_COMPLETE = 2;
@IntDef(prefix = "KEY_GESTURE_TYPE_", value = {
+ KEY_GESTURE_TYPE_SYSTEM_RESERVED,
KEY_GESTURE_TYPE_UNSPECIFIED,
KEY_GESTURE_TYPE_HOME,
KEY_GESTURE_TYPE_RECENT_APPS,
@@ -232,6 +236,26 @@ public final class KeyGestureEvent {
public @interface KeyGestureType {
}
+ /**
+ * Returns whether the key gesture type passed as argument is allowed for visible background
+ * users.
+ *
+ * @hide
+ */
+ public static boolean isVisibleBackgrounduserAllowedGesture(int keyGestureType) {
+ switch (keyGestureType) {
+ case KEY_GESTURE_TYPE_SLEEP:
+ case KEY_GESTURE_TYPE_WAKEUP:
+ case KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+ case KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
+ case KEY_GESTURE_TYPE_VOLUME_MUTE:
+ case KEY_GESTURE_TYPE_RECENT_APPS:
+ case KEY_GESTURE_TYPE_APP_SWITCH:
+ return false;
+ }
+ return true;
+ }
+
public KeyGestureEvent(@NonNull AidlKeyGestureEvent keyGestureEvent) {
this.mKeyGestureEvent = keyGestureEvent;
}
@@ -645,6 +669,8 @@ public final class KeyGestureEvent {
private static String keyGestureTypeToString(@KeyGestureType int value) {
switch (value) {
+ case KEY_GESTURE_TYPE_SYSTEM_RESERVED:
+ return "KEY_GESTURE_TYPE_SYSTEM_RESERVED";
case KEY_GESTURE_TYPE_UNSPECIFIED:
return "KEY_GESTURE_TYPE_UNSPECIFIED";
case KEY_GESTURE_TYPE_HOME:
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2bc6ab5a18e9..d5640221e6bd 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2783,4 +2783,12 @@ public final class Debug
*/
public static native boolean logAllocatorStats();
+ /**
+ * Return the amount of memory (in kB) allocated by kernel drivers through CMA.
+ * @return a non-negative value or -1 on error.
+ *
+ * @hide
+ */
+ public static native long getKernelCmaUsageKb();
+
}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 12080ca511b2..45a7afa014a1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -706,11 +706,11 @@ public class GraphicsEnvironment {
* @param context
*/
public void showAngleInUseDialogBox(Context context) {
- if (!shouldShowAngleInUseDialogBox(context)) {
+ if (!mShouldUseAngle) {
return;
}
- if (!mShouldUseAngle) {
+ if (!shouldShowAngleInUseDialogBox(context)) {
return;
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 0879118ff856..4aa74621bd62 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -593,11 +593,11 @@ public final class Parcel {
*/
public final void recycle() {
if (mRecycled) {
- Log.wtf(TAG, "Recycle called on unowned Parcel. (recycle twice?) Here: "
+ String error = "Recycle called on unowned Parcel. (recycle twice?) Here: "
+ Log.getStackTraceString(new Throwable())
- + " Original recycle call (if DEBUG_RECYCLE): ", mStack);
-
- return;
+ + " Original recycle call (if DEBUG_RECYCLE): ";
+ Log.wtf(TAG, error, mStack);
+ throw new IllegalStateException(error, mStack);
}
mRecycled = true;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 9085fe09bdaa..a58fea891851 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -278,7 +278,7 @@ public final class ServiceManager {
return service;
} else {
return Binder.allowBlocking(
- getIServiceManager().checkService(name).getServiceWithMetadata().service);
+ getIServiceManager().checkService2(name).getServiceWithMetadata().service);
}
} catch (RemoteException e) {
Log.e(TAG, "error in checkService", e);
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 7ea521ec5dd4..a5aa1b3efcd2 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -62,16 +62,23 @@ class ServiceManagerProxy implements IServiceManager {
@UnsupportedAppUsage
public IBinder getService(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return checkService(name).getServiceWithMetadata().service;
+ return checkService2(name).getServiceWithMetadata().service;
}
public Service getService2(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return checkService(name);
+ return checkService2(name);
}
- public Service checkService(String name) throws RemoteException {
- return mServiceManager.checkService(name);
+ // TODO(b/355394904): This function has been deprecated, please use checkService2 instead.
+ @UnsupportedAppUsage
+ public IBinder checkService(String name) throws RemoteException {
+ // Same as checkService (old versions of servicemanager had both methods).
+ return checkService2(name).getServiceWithMetadata().service;
+ }
+
+ public Service checkService2(String name) throws RemoteException {
+ return mServiceManager.checkService2(name);
}
public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 11dddfb24ad5..baaaa464a4cf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10525,6 +10525,15 @@ public final class Settings {
public static final String SCREENSAVER_ACTIVATE_ON_SLEEP = "screensaver_activate_on_sleep";
/**
+ * If screensavers are enabled, whether the screensaver should be
+ * automatically launched when the device is stationary and upright.
+ * @hide
+ */
+ @Readable
+ public static final String SCREENSAVER_ACTIVATE_ON_POSTURED =
+ "screensaver_activate_on_postured";
+
+ /**
* If screensavers are enabled, the default screensaver component.
* @hide
*/
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index ebb6fb451699..4a9e945e62a9 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -42,6 +42,16 @@ flag {
}
flag {
+ name: "secure_array_zeroization"
+ namespace: "platform_security"
+ description: "Enable secure array zeroization"
+ bug: "320392352"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "deprecate_fsv_sig"
namespace: "hardware_backed_security"
description: "Feature flag for deprecating .fsv_sig"
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index dfc11dcb5427..d3a230d1335d 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -77,3 +77,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "allow_dream_when_postured"
+ namespace: "systemui"
+ description: "Allow dreaming when device is stationary and upright"
+ bug: "383208131"
+}
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index b21e85aeeb6a..da3a817f0341 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -514,10 +514,14 @@ public final class PointerIcon implements Parcelable {
final TypedArray a = resources.obtainAttributes(
parser, com.android.internal.R.styleable.PointerIcon);
bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
- hotSpotX = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0)
- * pointerScale;
- hotSpotY = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0)
- * pointerScale;
+ // Cast the hotspot dimensions to int before scaling to match the scaling logic of
+ // the bitmap, whose intrinsic size is also an int before it is scaled.
+ final int unscaledHotSpotX =
+ (int) a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+ final int unscaledHotSpotY =
+ (int) a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+ hotSpotX = unscaledHotSpotX * pointerScale;
+ hotSpotY = unscaledHotSpotY * pointerScale;
a.recycle();
} catch (Exception ex) {
throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 71a832d84f08..99fe0cbdca25 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -18,7 +18,6 @@ package android.widget;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.graphics.Paint.NEW_FONT_VARIATION_MANAGEMENT;
import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
import static android.view.ContentInfo.SOURCE_CLIPBOARD;
@@ -5544,13 +5543,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
}
- final boolean useFontVariationStore = Flags.typefaceRedesignReadonly()
- && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
boolean effective;
- if (useFontVariationStore) {
+ if (Flags.typefaceRedesignReadonly()) {
if (mFontWeightAdjustment != 0
&& mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
- mTextPaint.setFontVariationSettings(fontVariationSettings, mFontWeightAdjustment);
+ List<FontVariationAxis> axes = FontVariationAxis.fromFontVariationSettingsForList(
+ fontVariationSettings);
+ if (axes == null) {
+ return false; // invalid format of the font variation settings.
+ }
+ boolean wghtAdjusted = false;
+ for (int i = 0; i < axes.size(); ++i) {
+ FontVariationAxis axis = axes.get(i);
+ if (axis.getOpenTypeTagValue() == 0x77676874 /* wght */) {
+ axes.set(i, new FontVariationAxis("wght",
+ Math.clamp(axis.getStyleValue() + mFontWeightAdjustment,
+ FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
+ wghtAdjusted = true;
+ }
+ }
+ if (!wghtAdjusted) {
+ axes.add(new FontVariationAxis("wght",
+ Math.clamp(400 + mFontWeightAdjustment,
+ FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
+ }
+ mTextPaint.setFontVariationSettings(
+ FontVariationAxis.toFontVariationSettings(axes));
} else {
mTextPaint.setFontVariationSettings(fontVariationSettings);
}
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 84ce247264f6..bd711fc79083 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -121,6 +121,14 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
public static final int FEATURE_WINDOWING_LAYER = FEATURE_SYSTEM_FIRST + 9;
/**
+ * Display area for rendering app zoom out. When there are multiple layers on the screen,
+ * we want to render these layers based on a depth model. Here we zoom out the layer behind,
+ * whether it's an app or the homescreen.
+ * @hide
+ */
+ public static final int FEATURE_APP_ZOOM_OUT = FEATURE_SYSTEM_FIRST + 10;
+
+ /**
* The last boundary of display area for system features
*/
public static final int FEATURE_SYSTEM_LAST = 10_000;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index de3e0d3faf43..7a1078f8718f 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -415,6 +415,17 @@ flag {
}
flag {
+ name: "keep_app_window_hide_while_locked"
+ namespace: "windowing_frontend"
+ description: "Do not let app window visible while device is locked"
+ is_fixed_read_only: true
+ bug: "378088391"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "port_window_size_animation"
namespace: "windowing_frontend"
description: "Port window-resize animation from legacy to shell"
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index c9c4be1e2c93..dc440e36ca0d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -19,6 +19,7 @@ package com.android.internal.os;
import static android.os.BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
import static android.os.BatteryStats.HistoryItem.EVENT_FLAG_START;
import static android.os.BatteryStats.HistoryItem.EVENT_STATE_CHANGE;
+import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -215,6 +216,7 @@ public class BatteryStatsHistory {
private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
private byte mLastHistoryStepLevel = 0;
private boolean mMutable = true;
+ private int mIteratorCookie;
private final BatteryStatsHistory mWritableHistory;
private static class BatteryHistoryFile implements Comparable<BatteryHistoryFile> {
@@ -289,6 +291,7 @@ public class BatteryStatsHistory {
}
void load() {
+ Trace.asyncTraceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.load", 0);
mDirectory.mkdirs();
if (!mDirectory.exists()) {
Slog.wtf(TAG, "HistoryDir does not exist:" + mDirectory.getPath());
@@ -325,8 +328,11 @@ public class BatteryStatsHistory {
}
} finally {
unlock();
+ Trace.asyncTraceEnd(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.load", 0);
}
});
+ } else {
+ Trace.asyncTraceEnd(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.load", 0);
}
}
@@ -418,6 +424,7 @@ public class BatteryStatsHistory {
}
void writeToParcel(Parcel out, boolean useBlobs) {
+ Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.writeToParcel");
lock();
try {
final long start = SystemClock.uptimeMillis();
@@ -443,6 +450,7 @@ public class BatteryStatsHistory {
}
} finally {
unlock();
+ Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
}
}
@@ -482,34 +490,39 @@ public class BatteryStatsHistory {
}
private void cleanup() {
- if (mDirectory == null) {
- return;
- }
-
- if (!tryLock()) {
- mCleanupNeeded = true;
- return;
- }
-
- mCleanupNeeded = false;
+ Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.cleanup");
try {
- // if free disk space is less than 100MB, delete oldest history file.
- if (!hasFreeDiskSpace(mDirectory)) {
- BatteryHistoryFile oldest = mHistoryFiles.remove(0);
- oldest.atomicFile.delete();
+ if (mDirectory == null) {
+ return;
+ }
+
+ if (!tryLock()) {
+ mCleanupNeeded = true;
+ return;
}
- // if there is more history stored than allowed, delete oldest history files.
- int size = getSize();
- while (size > mMaxHistorySize) {
- BatteryHistoryFile oldest = mHistoryFiles.get(0);
- int length = (int) oldest.atomicFile.getBaseFile().length();
- oldest.atomicFile.delete();
- mHistoryFiles.remove(0);
- size -= length;
+ mCleanupNeeded = false;
+ try {
+ // if free disk space is less than 100MB, delete oldest history file.
+ if (!hasFreeDiskSpace(mDirectory)) {
+ BatteryHistoryFile oldest = mHistoryFiles.remove(0);
+ oldest.atomicFile.delete();
+ }
+
+ // if there is more history stored than allowed, delete oldest history files.
+ int size = getSize();
+ while (size > mMaxHistorySize) {
+ BatteryHistoryFile oldest = mHistoryFiles.get(0);
+ int length = (int) oldest.atomicFile.getBaseFile().length();
+ oldest.atomicFile.delete();
+ mHistoryFiles.remove(0);
+ size -= length;
+ }
+ } finally {
+ unlock();
}
} finally {
- unlock();
+ Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
}
}
}
@@ -710,13 +723,18 @@ public class BatteryStatsHistory {
* in the system directory, so it is not safe while actively writing history.
*/
public BatteryStatsHistory copy() {
- synchronized (this) {
- // Make a copy of battery history to avoid concurrent modification.
- Parcel historyBufferCopy = Parcel.obtain();
- historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.copy");
+ try {
+ synchronized (this) {
+ // Make a copy of battery history to avoid concurrent modification.
+ Parcel historyBufferCopy = Parcel.obtain();
+ historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
- return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
- null, mEventLogger, this);
+ return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null,
+ null, null, mEventLogger, this);
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
}
}
@@ -826,7 +844,7 @@ public class BatteryStatsHistory {
*/
@NonNull
public BatteryStatsHistoryIterator iterate(long startTimeMs, long endTimeMs) {
- if (mMutable) {
+ if (mMutable || mIteratorCookie != 0) {
return copy().iterate(startTimeMs, endTimeMs);
}
@@ -837,7 +855,12 @@ public class BatteryStatsHistory {
mCurrentParcel = null;
mCurrentParcelEnd = 0;
mParcelIndex = 0;
- return new BatteryStatsHistoryIterator(this, startTimeMs, endTimeMs);
+ BatteryStatsHistoryIterator iterator = new BatteryStatsHistoryIterator(
+ this, startTimeMs, endTimeMs);
+ mIteratorCookie = System.identityHashCode(iterator);
+ Trace.asyncTraceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.iterate",
+ mIteratorCookie);
+ return iterator;
}
/**
@@ -848,6 +871,9 @@ public class BatteryStatsHistory {
if (mHistoryDir != null) {
mHistoryDir.unlock();
}
+ Trace.asyncTraceEnd(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.iterate",
+ mIteratorCookie);
+ mIteratorCookie = 0;
}
/**
@@ -949,28 +975,33 @@ public class BatteryStatsHistory {
* @return true if success, false otherwise.
*/
public boolean readFileToParcel(Parcel out, AtomicFile file) {
- byte[] raw = null;
+ Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.read");
try {
- final long start = SystemClock.uptimeMillis();
- raw = file.readFully();
- if (DEBUG) {
- Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
- + " duration ms:" + (SystemClock.uptimeMillis() - start));
+ byte[] raw = null;
+ try {
+ final long start = SystemClock.uptimeMillis();
+ raw = file.readFully();
+ if (DEBUG) {
+ Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
+ + " duration ms:" + (SystemClock.uptimeMillis() - start));
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
+ return false;
}
- } catch (Exception e) {
- Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
- return false;
- }
- out.unmarshall(raw, 0, raw.length);
- out.setDataPosition(0);
- if (!verifyVersion(out)) {
- return false;
+ out.unmarshall(raw, 0, raw.length);
+ out.setDataPosition(0);
+ if (!verifyVersion(out)) {
+ return false;
+ }
+ // skip monotonic time field.
+ out.readLong();
+ // skip monotonic size field
+ out.readLong();
+ return true;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
}
- // skip monotonic time field.
- out.readLong();
- // skip monotonic size field
- out.readLong();
- return true;
}
/**
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 7f7ea8b28546..37500766a4ac 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -36,7 +36,6 @@ import com.android.internal.org.bouncycastle.cms.SignerInformationVerifier;
import com.android.internal.org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import com.android.internal.org.bouncycastle.operator.OperatorCreationException;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
@@ -53,12 +52,6 @@ import java.security.cert.X509Certificate;
public abstract class VerityUtils {
private static final String TAG = "VerityUtils";
- /**
- * File extension of the signature file. For example, foo.apk.fsv_sig is the signature file of
- * foo.apk.
- */
- public static final String FSVERITY_SIGNATURE_FILE_EXTENSION = ".fsv_sig";
-
/** SHA256 hash size. */
private static final int HASH_SIZE_BYTES = 32;
@@ -67,16 +60,6 @@ public abstract class VerityUtils {
|| SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
}
- /** Returns true if the given file looks like containing an fs-verity signature. */
- public static boolean isFsveritySignatureFile(File file) {
- return file.getName().endsWith(FSVERITY_SIGNATURE_FILE_EXTENSION);
- }
-
- /** Returns the fs-verity signature file path of the given file. */
- public static String getFsveritySignatureFilePath(String filePath) {
- return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION;
- }
-
/** Enables fs-verity for the file without signature. */
public static void setUpFsverity(@NonNull String filePath) throws IOException {
int errno = enableFsverityNative(filePath);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index f14e1f63cdf6..ec0954d5590a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -239,4 +239,7 @@ interface IStatusBarService
/** Unbundle a categorized notification */
void unbundleNotification(String key);
+
+ /** Rebundle an (un)categorized notification */
+ void rebundleNotification(String key);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 39ddea614ee4..74707703f5f2 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -65,6 +65,7 @@ import android.util.SparseLongArray;
import android.view.InputDevice;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.google.android.collect.Lists;
@@ -75,6 +76,7 @@ import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -292,6 +294,56 @@ public class LockPatternUtils {
}
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static byte[] newNonMovableByteArray(int length) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ return new byte[length];
+ }
+ return ArrayUtils.newNonMovableByteArray(length);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static char[] newNonMovableCharArray(int length) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ return new char[length];
+ }
+ return ArrayUtils.newNonMovableCharArray(length);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static void zeroize(byte[] array) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ if (array != null) {
+ Arrays.fill(array, (byte) 0);
+ }
+ return;
+ }
+ ArrayUtils.zeroize(array);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static void zeroize(char[] array) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ if (array != null) {
+ Arrays.fill(array, (char) 0);
+ }
+ return;
+ }
+ ArrayUtils.zeroize(array);
+ }
+
@UnsupportedAppUsage
public DevicePolicyManager getDevicePolicyManager() {
if (mDevicePolicyManager == null) {
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 54b9a225f944..92ce990c67df 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -246,7 +246,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
*/
public void zeroize() {
if (mCredential != null) {
- Arrays.fill(mCredential, (byte) 0);
+ LockPatternUtils.zeroize(mCredential);
mCredential = null;
}
}
@@ -346,7 +346,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
- Arrays.fill(saltedPassword, (byte) 0);
+ LockPatternUtils.zeroize(saltedPassword);
return HexEncoding.encodeToString(ArrayUtils.concat(sha1, md5));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 80bc4fd89c8d..dd12f69a56fb 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -56,8 +56,6 @@ public class NotificationExpandButton extends FrameLayout {
private int mDefaultTextColor;
private int mHighlightPillColor;
private int mHighlightTextColor;
- // Track whether this ever had mExpanded = true, so that we don't highlight it anymore.
- private boolean mWasExpanded = false;
public NotificationExpandButton(Context context) {
this(context, null, 0, 0);
@@ -136,7 +134,6 @@ public class NotificationExpandButton extends FrameLayout {
int contentDescriptionId;
if (mExpanded) {
if (notificationsRedesignTemplates()) {
- mWasExpanded = true;
drawableId = R.drawable.ic_notification_2025_collapse;
} else {
drawableId = R.drawable.ic_collapse_notification;
@@ -156,8 +153,6 @@ public class NotificationExpandButton extends FrameLayout {
if (!notificationsRedesignTemplates()) {
// changing the expanded state can affect the number display
updateNumber();
- } else {
- updateColors();
}
}
@@ -197,43 +192,22 @@ public class NotificationExpandButton extends FrameLayout {
);
}
- /**
- * Use highlight colors for the expander for groups (when the number is showing) that haven't
- * been opened before, as long as the colors are available.
- */
- private boolean shouldBeHighlighted() {
- return !mWasExpanded && shouldShowNumber()
- && mHighlightPillColor != 0 && mHighlightTextColor != 0;
- }
-
private void updateColors() {
- if (notificationsRedesignTemplates()) {
- if (shouldBeHighlighted()) {
+ if (shouldShowNumber()) {
+ if (mHighlightPillColor != 0) {
mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
- mIconView.setColorFilter(mHighlightTextColor);
+ }
+ mIconView.setColorFilter(mHighlightTextColor);
+ if (mHighlightTextColor != 0) {
mNumberView.setTextColor(mHighlightTextColor);
- } else {
- mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
- mIconView.setColorFilter(mDefaultTextColor);
- mNumberView.setTextColor(mDefaultTextColor);
}
} else {
- if (shouldShowNumber()) {
- if (mHighlightPillColor != 0) {
- mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
- }
- mIconView.setColorFilter(mHighlightTextColor);
- if (mHighlightTextColor != 0) {
- mNumberView.setTextColor(mHighlightTextColor);
- }
- } else {
- if (mDefaultPillColor != 0) {
- mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
- }
- mIconView.setColorFilter(mDefaultTextColor);
- if (mDefaultTextColor != 0) {
- mNumberView.setTextColor(mDefaultTextColor);
- }
+ if (mDefaultPillColor != 0) {
+ mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
+ }
+ mIconView.setColorFilter(mDefaultTextColor);
+ if (mDefaultTextColor != 0) {
+ mNumberView.setTextColor(mDefaultTextColor);
}
}
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 8cd7843fe1d9..904b73f41e70 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,15 @@ 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 com.android.internal.widget.NotificationProgressDrawable.DrawablePart;
+import com.android.internal.widget.NotificationProgressDrawable.DrawablePoint;
+import com.android.internal.widget.NotificationProgressDrawable.DrawableSegment;
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 +59,26 @@ 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 static final boolean DEBUG = false;
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<DrawablePart> mProgressDrawableParts = null;
@Nullable
private Drawable mTracker = null;
+ private boolean mHasTrackerIcon = false;
/** @see R.styleable#NotificationProgressBar_trackerHeight */
private final int mTrackerHeight;
@@ -76,7 +87,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;
@@ -96,20 +113,21 @@ public final class NotificationProgressBar extends ProgressBar {
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- final TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.NotificationProgressBar, defStyleAttr, defStyleRes);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.NotificationProgressBar, defStyleAttr, defStyleRes);
saveAttributeDataForStyleable(context, R.styleable.NotificationProgressBar, attrs, a,
defStyleAttr,
defStyleRes);
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);
@@ -126,8 +144,7 @@ public final class NotificationProgressBar extends ProgressBar {
*/
@RemotableViewMethod
public void setProgressModel(@Nullable Bundle bundle) {
- Preconditions.checkArgument(bundle != null,
- "Bundle shouldn't be null");
+ Preconditions.checkArgument(bundle != null, "Bundle shouldn't be null");
mProgressModel = NotificationProgressModel.fromBundle(bundle);
final boolean isIndeterminate = mProgressModel.isIndeterminate();
@@ -137,20 +154,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 +222,7 @@ public final class NotificationProgressBar extends ProgressBar {
} else {
progressTrackerDrawable = null;
}
- return () -> {
- setTracker(progressTrackerDrawable);
- };
+ return () -> setTracker(progressTrackerDrawable);
}
private void setTracker(@Nullable Drawable tracker) {
@@ -226,8 +246,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 +319,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 +328,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 +338,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 +347,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 +357,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();
}
@@ -350,8 +384,7 @@ public final class NotificationProgressBar extends ProgressBar {
super.drawableStateChanged();
final Drawable tracker = mTracker;
- if (tracker != null && tracker.isStateful()
- && tracker.setState(getDrawableState())) {
+ if (tracker != null && tracker.isStateful() && tracker.setState(getDrawableState())) {
invalidateDrawable(tracker);
}
}
@@ -372,6 +405,65 @@ 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() {
+ if (DEBUG) {
+ 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) {
+ if (DEBUG) {
+ Log.d(TAG, "Clearing mProgressDrawableParts");
+ }
+ mProgressDrawableParts.clear();
+ mNotificationProgressDrawable.setParts(mProgressDrawableParts);
+ }
+ return;
+ }
+
+ mProgressDrawableParts = processAndConvertToDrawableParts(
+ mParts,
+ width,
+ mNotificationProgressDrawable.getSegSegGap(),
+ mNotificationProgressDrawable.getSegPointGap(),
+ mNotificationProgressDrawable.getPointRadius(),
+ mHasTrackerIcon
+ );
+ Pair<List<DrawablePart>, Float> p = maybeStretchAndRescaleSegments(
+ mParts,
+ mProgressDrawableParts,
+ mNotificationProgressDrawable.getSegmentMinWidth(),
+ mNotificationProgressDrawable.getPointRadius(),
+ getProgressFraction(),
+ width,
+ mProgressModel.isStyledByProgress(),
+ mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap());
+
+ if (DEBUG) {
+ 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 +494,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;
@@ -416,19 +508,19 @@ public final class NotificationProgressBar extends ProgressBar {
/**
* Updates the tracker drawable bounds.
*
- * @param w Width of the view, including padding
- * @param tracker Drawable used for the tracker
- * @param scale 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.
+ * @param w Width of the view, including padding
+ * @param tracker Drawable used for the tracker
+ * @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) {
@@ -448,8 +540,8 @@ public final class NotificationProgressBar extends ProgressBar {
if (background != null) {
final int bkgOffsetX = mPaddingLeft;
final int bkgOffsetY = mPaddingTop;
- background.setHotspotBounds(left + bkgOffsetX, top + bkgOffsetY,
- right + bkgOffsetX, bottom + bkgOffsetY);
+ background.setHotspotBounds(left + bkgOffsetX, top + bkgOffsetY, right + bkgOffsetX,
+ bottom + bkgOffsetY);
}
// Canvas will be translated, so 0,0 is where we start drawing
@@ -482,7 +574,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 +623,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 +633,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 +661,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 +674,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);
+ final Map<Integer, ProgressStyle.Segment> startToSplitSegmentMap = 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;
@@ -610,8 +699,7 @@ public final class NotificationProgressBar extends ProgressBar {
final ProgressStyle.Segment prevSeg = startToSegmentMap.get(prevSegStart);
final ProgressStyle.Segment leftSeg = new ProgressStyle.Segment(
- pos - prevSegStart).setColor(
- prevSeg.getColor());
+ pos - prevSegStart).setColor(prevSeg.getColor());
final ProgressStyle.Segment rightSeg = new ProgressStyle.Segment(
prevSegStart + prevSeg.getLength() - pos).setColor(prevSeg.getColor());
@@ -624,32 +712,21 @@ 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));
+ parts.add(new Segment((float) seg.getLength() / progressMax, seg.getColor()));
}
}
@@ -660,11 +737,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 +771,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 +784,392 @@ 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 DrawablePart}.
+ */
+ @VisibleForTesting
+ public static List<DrawablePart> processAndConvertToDrawableParts(
+ List<Part> parts,
+ float totalWidth,
+ float segSegGap,
+ float segPointGap,
+ float pointRadius,
+ boolean hasTrackerIcon
+ ) {
+ List<DrawablePart> 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 DrawableSegment(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 DrawablePoint(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 DrawablePart} data and convert to a pair of:
+ * - list of processed {@code DrawablePart}.
+ * - location of progress on the stretched and rescaled progress bar.
+ */
+ @VisibleForTesting
+ public static Pair<List<DrawablePart>, Float> maybeStretchAndRescaleSegments(
+ List<Part> parts,
+ List<DrawablePart> drawableParts,
+ float segmentMinWidth,
+ float pointRadius,
+ float progressFraction,
+ float totalWidth,
+ boolean isStyledByProgress,
+ float progressGap
+ ) {
+ final List<DrawableSegment> drawableSegments = drawableParts
+ .stream()
+ .filter(DrawableSegment.class::isInstance)
+ .map(DrawableSegment.class::cast)
+ .toList();
+ float totalExcessWidth = 0;
+ float totalPositiveExcessWidth = 0;
+ for (DrawableSegment 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 DrawablePart drawablePart = drawableParts.get(iPart);
+ if (drawablePart instanceof DrawableSegment 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 DrawablePoint 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 drawable segment with the progress value in its
+ * range. Style the drawable parts after process with reduced opacity and segment height.
+ */
+ private static Pair<List<DrawablePart>, Float> maybeSplitDrawableSegmentsByProgress(
+ // Needed to get the original segment start and end positions in pixels.
+ List<Part> parts,
+ List<DrawablePart> 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<DrawablePart> splitDrawableParts = new ArrayList<>();
+ boolean styleRemainingParts = false;
+ for (int iPart = 0; iPart < nParts; iPart++) {
+ final DrawablePart drawablePart = drawableParts.get(iPart);
+ if (drawablePart instanceof DrawablePoint drawablePoint) {
+ final int color = maybeGetFadedColor(drawablePoint.getColor(), styleRemainingParts);
+ splitDrawableParts.add(
+ new DrawablePoint(drawablePoint.getStart(), drawablePoint.getEnd(), color));
+ }
+ if (iPart == iPartFirstSegmentToStyle) styleRemainingParts = true;
+ if (drawablePart instanceof DrawableSegment drawableSegment) {
+ if (iPart == iPartSegmentToSplit) {
+ if (rescaledProgressX <= drawableSegment.getStart()) {
+ styleRemainingParts = true;
+ final int color = maybeGetFadedColor(drawableSegment.getColor(), true);
+ splitDrawableParts.add(new DrawableSegment(drawableSegment.getStart(),
+ drawableSegment.getEnd(), color, true));
+ } else if (drawableSegment.getStart() < rescaledProgressX
+ && rescaledProgressX < drawableSegment.getEnd()) {
+ splitDrawableParts.add(new DrawableSegment(drawableSegment.getStart(),
+ rescaledProgressX - progressGap, drawableSegment.getColor()));
+ final int color = maybeGetFadedColor(drawableSegment.getColor(), true);
+ splitDrawableParts.add(
+ new DrawableSegment(rescaledProgressX, drawableSegment.getEnd(),
+ color, true));
+ styleRemainingParts = true;
+ } else {
+ splitDrawableParts.add(new DrawableSegment(drawableSegment.getStart(),
+ drawableSegment.getEnd(), drawableSegment.getColor()));
+ styleRemainingParts = true;
+ }
+ } else {
+ final int color = maybeGetFadedColor(drawableSegment.getColor(),
+ styleRemainingParts);
+ splitDrawableParts.add(new DrawableSegment(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.
+ 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..4ece81c24edc 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,22 +48,24 @@ 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
+ * @see DrawableSegment
+ * @see DrawablePoint
*/
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 ArrayList<DrawablePart> mParts = new ArrayList<>();
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,33 +81,37 @@ 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;
}
/**
* Set the segments and points that constitute the drawable.
*/
- public void setParts(List<Part> parts) {
+ public void setParts(List<DrawablePart> parts) {
mParts.clear();
mParts.addAll(parts);
@@ -116,51 +121,22 @@ public final class NotificationProgressDrawable extends Drawable {
/**
* Set the segments and points that constitute the drawable.
*/
- public void setParts(@NonNull Part... parts) {
+ public void setParts(@NonNull DrawablePart... parts) {
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);
- 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;
-
+ final DrawablePart part = mParts.get(iPart);
+ final float start = left + part.mStart;
+ final float end = left + part.mEnd;
+ if (part instanceof DrawableSegment segment) {
// 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);
- }
- }
- }
- }
+ } else if (part instanceof DrawablePoint point) {
+ // 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,57 @@ 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 drawable, which is either a {@link DrawableSegment} with non-zero
+ * length and varying drawing width, or a {@link DrawablePoint} 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 DrawablePart {
+ // 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 DrawablePart(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 +465,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;
+ DrawablePart that = (DrawablePart) 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 DrawableSegment extends DrawablePart {
+ /**
+ * 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 DrawableSegment(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 DrawableSegment(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;
+ if (!super.equals(other)) return false;
- Point that = (Point) other;
-
- if (!Objects.equals(this.mIcon, that.mIcon)) return false;
- if (this.mColor != that.mColor) return false;
+ DrawableSegment that = (DrawableSegment) 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 DrawablePoint extends DrawablePart {
+ public DrawablePoint(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 +562,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 +584,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 +603,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 +681,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/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index e6364a96bd9f..1e7bfe32ba79 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -111,8 +111,9 @@ static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_ass
class LoaderAssetsProvider : public AssetsProvider {
public:
static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
- return std::unique_ptr<AssetsProvider>{
- assets_provider ? new LoaderAssetsProvider(env, assets_provider) : nullptr};
+ return (!assets_provider) ? EmptyAssetsProvider::Create()
+ : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider(
+ env, assets_provider));
}
bool ForEachFile(const std::string& /* root_path */,
@@ -128,8 +129,8 @@ class LoaderAssetsProvider : public AssetsProvider {
return debug_name_;
}
- UpToDate IsUpToDate() const override {
- return UpToDate::Always;
+ bool IsUpToDate() const override {
+ return true;
}
~LoaderAssetsProvider() override {
@@ -211,7 +212,7 @@ class LoaderAssetsProvider : public AssetsProvider {
auto string_result = static_cast<jstring>(env->CallObjectMethod(
assets_provider_, gAssetsProviderOffsets.toString));
ScopedUtfChars str(env, string_result);
- debug_name_ = std::string(str.c_str());
+ debug_name_ = std::string(str.c_str(), str.size());
}
// The global reference to the AssetsProvider
@@ -242,10 +243,10 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
break;
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
- MultiAssetsProvider::Create(std::move(loader_assets)),
- property_flags);
- break;
+ apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
+ std::move(loader_assets),
+ property_flags);
+ break;
case FORMAT_DIRECTORY: {
auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
DirectoryAssetsProvider::Create(path.c_str()));
@@ -315,11 +316,10 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t
break;
}
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
- nullptr /* path */),
- MultiAssetsProvider::Create(std::move(loader_assets)),
- property_flags);
- break;
+ apk_assets = ApkAssets::LoadTable(
+ AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */),
+ std::move(loader_assets), property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -386,15 +386,12 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
break;
}
case FORMAT_ARSC:
- apk_assets =
- ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
- nullptr /* path */,
- static_cast<off64_t>(offset),
- static_cast<off64_t>(
- length)),
- MultiAssetsProvider::Create(std::move(loader_assets)),
- property_flags);
- break;
+ apk_assets = ApkAssets::LoadTable(
+ AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */,
+ static_cast<off64_t>(offset),
+ static_cast<off64_t>(length)),
+ std::move(loader_assets), property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -411,16 +408,13 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
}
static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
- auto apk_assets = ApkAssets::Load(MultiAssetsProvider::Create(
- LoaderAssetsProvider::Create(env, assets_provider)),
- flags);
- if (apk_assets == nullptr) {
- const std::string error_msg =
- base::StringPrintf("Failed to load empty assets with provider %p",
- (void*)assets_provider);
- jniThrowException(env, "java/io/IOException", error_msg.c_str());
- return 0;
- }
+ auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
+ if (apk_assets == nullptr) {
+ const std::string error_msg =
+ base::StringPrintf("Failed to load empty assets with provider %p", (void*)assets_provider);
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
return CreateGuardedApkAssets(std::move(apk_assets));
}
@@ -449,10 +443,10 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr)
return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
}
-static jint NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+static jboolean NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
auto apk_assets = scoped_apk_assets->get();
- return (jint)apk_assets->IsUpToDate();
+ return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
}
static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
@@ -564,7 +558,7 @@ static const JNINativeMethod gApkAssetsMethods[] = {
{"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
// @CriticalNative
- {"nativeIsUpToDate", "(J)I", (void*)NativeIsUpToDate},
+ {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
{"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
{"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
(void*)NativeGetOverlayableInfo},
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index b1221ee38db3..68ef3d424d12 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -165,19 +165,25 @@ android_hardware_UsbDeviceConnection_control_request(JNIEnv *env, jobject thiz,
return -1;
}
- jbyte* bufferBytes = NULL;
- if (buffer) {
- bufferBytes = (jbyte*)env->GetPrimitiveArrayCritical(buffer, NULL);
+ bool is_dir_in = (requestType & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN;
+ std::unique_ptr<jbyte[]> bufferBytes(new (std::nothrow) jbyte[length]);
+ if (!bufferBytes) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return -1;
+ }
+
+ if (!is_dir_in && buffer) {
+ env->GetByteArrayRegion(buffer, start, length, bufferBytes.get());
}
- jint result = usb_device_control_transfer(device, requestType, request,
- value, index, bufferBytes + start, length, timeout);
+ jint bytes_transferred = usb_device_control_transfer(device, requestType, request,
+ value, index, bufferBytes.get(), length, timeout);
- if (bufferBytes) {
- env->ReleasePrimitiveArrayCritical(buffer, bufferBytes, 0);
+ if (bytes_transferred > 0 && is_dir_in) {
+ env->SetByteArrayRegion(buffer, start, bytes_transferred, bufferBytes.get());
}
- return result;
+ return bytes_transferred;
}
static jint
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 3c2dccd451d4..9ef17e82c38e 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -729,6 +729,17 @@ static jlong android_os_Debug_getGpuPrivateMemoryKb(JNIEnv* env, jobject clazz)
return gpuPrivateMem / 1024;
}
+static jlong android_os_Debug_getKernelCmaUsageKb(JNIEnv* env, jobject clazz) {
+ jlong totalKernelCmaUsageKb = -1;
+ uint64_t size;
+
+ if (meminfo::ReadKernelCmaUsageKb(&size)) {
+ totalKernelCmaUsageKb = size;
+ }
+
+ return totalKernelCmaUsageKb;
+}
+
static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) {
jlong dmabufPss = 0;
std::vector<dmabufinfo::DmaBuffer> dmabufs;
@@ -836,6 +847,7 @@ static const JNINativeMethod gMethods[] = {
{"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb},
{"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack},
{"logAllocatorStats", "()Z", (void*)android_os_Debug_logAllocatorStats},
+ {"getKernelCmaUsageKb", "()J", (void*)android_os_Debug_getKernelCmaUsageKb},
};
int register_android_os_Debug(JNIEnv *env)
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4f7ba9388a1d..5d0b340ac839 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -580,6 +580,7 @@ message SecureSettingsProto {
optional SettingProto activate_on_dock = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto activate_on_sleep = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto default_component = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto activate_on_postured = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Screensaver screensaver = 47;
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/values-round-watch/dimens.xml b/core/res/res/values-round-watch/dimens.xml
index f288b41fb556..59ee554798bc 100644
--- a/core/res/res/values-round-watch/dimens.xml
+++ b/core/res/res/values-round-watch/dimens.xml
@@ -26,6 +26,6 @@
<item name="input_extract_action_button_height" type="dimen">32dp</item>
<item name="input_extract_action_icon_padding" type="dimen">5dp</item>
- <item name="global_actions_vertical_padding_percentage" type="fraction">20.8%</item>
+ <item name="global_actions_vertical_padding_percentage" type="fraction">21.8%</item>
<item name="global_actions_horizontal_padding_percentage" type="fraction">5.2%</item>
</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 ce9a0c636d62..e14cffd72b0c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2766,6 +2766,9 @@
<bool name="config_dreamsActivatedOnDockByDefault">true</bool>
<!-- If supported and enabled, are dreams activated when asleep and charging? (by default) -->
<bool name="config_dreamsActivatedOnSleepByDefault">false</bool>
+ <!-- If supported and enabled, are dreams enabled while device is stationary and upright?
+ (by default) -->
+ <bool name="config_dreamsActivatedOnPosturedByDefault">false</bool>
<!-- ComponentName of the default dream (Settings.Secure.DEFAULT_SCREENSAVER_COMPONENT) -->
<string name="config_dreamsDefaultComponent" translatable="false">com.android.deskclock/com.android.deskclock.Screensaver</string>
<!-- ComponentNames of the dreams that we should hide -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 4f7351c7cc4d..d6b8704a978b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -899,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/symbols.xml b/core/res/res/values/symbols.xml
index f89ca44cce30..5f6619d4e4cc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2330,6 +2330,7 @@
<java-symbol type="bool" name="config_dreamsEnabledOnBattery" />
<java-symbol type="bool" name="config_dreamsActivatedOnDockByDefault" />
<java-symbol type="bool" name="config_dreamsActivatedOnSleepByDefault" />
+ <java-symbol type="bool" name="config_dreamsActivatedOnPosturedByDefault" />
<java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenPowered" />
<java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" />
<java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
@@ -3950,6 +3951,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" />
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/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index c45080fb5e26..5fd4ffc7329a 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -10,6 +10,9 @@ per-file PowerManager*.java = file:/services/core/java/com/android/server/power/
# PerformanceHintManager
per-file PerformanceHintManagerTest.java = file:/ADPF_OWNERS
+# SystemHealthManager
+per-file SystemHealthManagerUnitTest.java = file:/ADPF_OWNERS
+
# Caching
per-file IpcDataCache* = file:/PERFORMANCE_OWNERS
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index da9d687ee2b0..3e6520106ab0 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -361,7 +361,11 @@ public class ParcelTest {
p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie");
p.recycle();
- assertThat(p.getClassCookie(ParcelTest.class)).isNull();
+
+ // cannot access Parcel after it's recycled!
+ // this test is equivalent to checking hasClassCookie false
+ // after obtaining above
+ // assertThat(p.getClassCookie(ParcelTest.class)).isNull();
}
@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..5df2c1279eb8 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,16 @@ 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 com.android.internal.widget.NotificationProgressDrawable.DrawablePart;
+import com.android.internal.widget.NotificationProgressDrawable.DrawablePoint;
+import com.android.internal.widget.NotificationProgressDrawable.DrawableSegment;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,183 +41,287 @@ 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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(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<DrawablePart>, 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 DrawableSegment(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> 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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 300, Color.RED)));
- List<Part> expected = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
- assertThat(parts).isEqualTo(expected);
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, 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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 146, Color.RED),
+ new DrawableSegment(150, 300, Color.GREEN)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+ Pair<List<DrawablePart>, 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 DrawableSegment(0, 146, Color.RED),
+ new DrawableSegment(150, 180, Color.GREEN),
+ new DrawableSegment(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> expectedParts = new ArrayList<>(
+ List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
- List<Part> expected = new ArrayList<>(List.of(
- new Segment(0.50f, Color.RED),
- new Segment(0.10f, Color.GREEN),
- new Segment(0.40f, fadedGreen, true)));
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = false;
+
+ List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 146, Color.RED),
+ new DrawableSegment(150, 300, Color.GREEN)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+ Pair<List<DrawablePart>, 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;
+ expectedDrawableParts = new ArrayList<>(List.of(new DrawableSegment(0, 146, Color.RED),
+ new DrawableSegment(150, 176, Color.GREEN),
+ new DrawableSegment(180, 300, fadedGreen, true)));
+
+ 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 +331,68 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Segment(0.15f, Color.BLUE), new Point(Color.RED),
+ new Segment(0.10f, Color.BLUE), new Point(Color.BLUE),
+ new Segment(0.35f, Color.BLUE), 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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 35, Color.BLUE),
+ new DrawablePoint(39, 51, Color.RED),
+ new DrawableSegment(55, 65, Color.BLUE),
+ new DrawablePoint(69, 81, Color.BLUE),
+ new DrawableSegment(85, 170, Color.BLUE),
+ new DrawablePoint(174, 186, Color.BLUE),
+ new DrawableSegment(190, 215, Color.BLUE),
+ new DrawablePoint(219, 231, Color.YELLOW),
+ new DrawableSegment(235, 300, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
boolean isStyledByProgress = true;
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
// Colors with 40% opacity
int fadedBlue = 0x660000FF;
int fadedYellow = 0x66FFFF00;
-
- List<Part> expected = new ArrayList<>(List.of(
- new Segment(0.15f, Color.BLUE),
- new Point(null, Color.RED),
- new Segment(0.10f, Color.BLUE),
- new Point(null, 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)));
-
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
-
- assertThat(parts).isEqualTo(expected);
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
+ new DrawablePoint(38.219177F, 50.219177F, Color.RED),
+ new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
+ new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
+ new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
+ new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
+ new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
+ new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
+ new DrawableSegment(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 +403,68 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
+ new Segment(0.10f, Color.RED), new Point(Color.BLUE),
+ new Segment(0.25f, Color.RED), new Segment(0.10f, Color.GREEN),
+ 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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
+ new DrawableSegment(55, 65, Color.RED),
+ new DrawablePoint(69, 81, Color.BLUE),
+ new DrawableSegment(85, 146, Color.RED),
+ new DrawableSegment(150, 170, Color.GREEN),
+ new DrawablePoint(174, 186, Color.BLUE),
+ new DrawableSegment(190, 215, Color.GREEN),
+ new DrawablePoint(219, 231, Color.YELLOW),
+ new DrawableSegment(235, 300, Color.GREEN)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
boolean isStyledByProgress = true;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
// Colors with 40% opacity
int fadedGreen = 0x6600FF00;
int fadedYellow = 0x66FFFF00;
-
- List<Part> expected = new ArrayList<>(List.of(
- new Segment(0.15f, Color.RED),
- new Point(null, Color.RED),
- new Segment(0.10f, Color.RED),
- new Point(null, 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)));
-
- assertThat(parts).isEqualTo(expected);
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 34.095238F, Color.RED),
+ new DrawablePoint(38.095238F, 50.095238F, Color.RED),
+ new DrawableSegment(54.095238F, 70.09524F, Color.RED),
+ new DrawablePoint(74.09524F, 86.09524F, Color.BLUE),
+ new DrawableSegment(90.09524F, 148.9524F, Color.RED),
+ new DrawableSegment(152.95238F, 172.7619F, Color.GREEN),
+ new DrawablePoint(176.7619F, 188.7619F, Color.BLUE),
+ new DrawableSegment(192.7619F, 217.33333F, fadedGreen, true),
+ new DrawablePoint(221.33333F, 233.33333F, fadedYellow),
+ new DrawableSegment(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 +474,223 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+ progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
+ new Segment(0.10f, Color.RED), new Point(Color.BLUE),
+ new Segment(0.25f, Color.RED), new Segment(0.25f, 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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
+ new DrawableSegment(55, 65, Color.RED),
+ new DrawablePoint(69, 81, Color.BLUE),
+ new DrawableSegment(85, 146, Color.RED),
+ new DrawableSegment(150, 215, Color.GREEN),
+ new DrawablePoint(219, 231, Color.YELLOW),
+ new DrawableSegment(235, 300, Color.GREEN)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
boolean isStyledByProgress = false;
- List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, progressMax, isStyledByProgress);
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 34.296295F, Color.RED),
+ new DrawablePoint(38.296295F, 50.296295F, Color.RED),
+ new DrawableSegment(54.296295F, 70.296295F, Color.RED),
+ new DrawablePoint(74.296295F, 86.296295F, Color.BLUE),
+ new DrawableSegment(90.296295F, 149.62962F, Color.RED),
+ new DrawableSegment(153.62962F, 216.8148F, Color.GREEN),
+ new DrawablePoint(220.81482F, 232.81482F, Color.YELLOW),
+ new DrawableSegment(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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawablePoint(0, 12, Color.BLUE),
+ new DrawableSegment(16, 16, Color.BLUE),
+ new DrawableSegment(20, 56, Color.BLUE),
+ new DrawableSegment(60, 116, Color.BLUE),
+ new DrawableSegment(120, 200, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ expectedDrawableParts = new ArrayList<>(List.of(new DrawablePoint(0, 12, Color.BLUE),
+ new DrawableSegment(16, 32, Color.BLUE),
+ new DrawableSegment(36, 69.41936F, Color.BLUE),
+ new DrawableSegment(73.41936F, 124.25807F, Color.BLUE),
+ new DrawableSegment(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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawablePoint(0, 12, Color.BLUE),
+ new DrawableSegment(16, 16, Color.BLUE),
+ new DrawableSegment(20, 56, Color.BLUE),
+ new DrawableSegment(60, 116, Color.BLUE),
+ new DrawableSegment(120, 200, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 10;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ expectedDrawableParts = new ArrayList<>(List.of(new DrawablePoint(0, 12, Color.BLUE),
+ new DrawableSegment(16, 26, Color.BLUE),
+ new DrawableSegment(30, 64.169014F, Color.BLUE),
+ new DrawableSegment(68.169014F, 120.92958F, Color.BLUE),
+ new DrawableSegment(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<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawablePoint(0, 12, Color.BLUE),
+ new DrawableSegment(16, 36, Color.BLUE),
+ new DrawableSegment(40, 56, Color.BLUE),
+ new DrawableSegment(60, 116, Color.BLUE),
+ new DrawableSegment(120, 200, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 10;
+ boolean isStyledByProgress = true;
- List<Part> expected = new ArrayList<>(List.of(
- new Segment(0.15f, Color.RED),
- new Point(null, Color.RED),
- new Segment(0.10f, Color.RED),
- new Point(null, Color.BLUE),
- new Segment(0.25f, Color.RED),
- new Segment(0.25f, Color.GREEN),
- new Point(null, Color.YELLOW),
- new Segment(0.25f, Color.GREEN)));
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 200, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
- assertThat(parts).isEqualTo(expected);
+ assertThat(p.second).isEqualTo(200);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
}
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 50c95a9fa882..3378cc11d565 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -16,9 +16,9 @@
package android.graphics;
+import static com.android.text.flags.Flags.FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API;
import static com.android.text.flags.Flags.FLAG_VERTICAL_TEXT_LAYOUT;
import android.annotation.ColorInt;
@@ -34,7 +34,6 @@ import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.text.TextRunShaper;
import android.os.Build;
@@ -2100,14 +2099,6 @@ public class Paint {
}
/**
- * A change ID for new font variation settings management.
- * @hide
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = 36)
- public static final long NEW_FONT_VARIATION_MANAGEMENT = 361260253L;
-
- /**
* Sets TrueType or OpenType font variation settings. The settings string is constructed from
* multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
* and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
@@ -2136,16 +2127,12 @@ public class Paint {
* </li>
* </ul>
*
- * <p>Note: If the application that targets API 35 or before, this function mutates the
- * underlying typeface instance.
- *
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
- * @return If the application that targets API 36 or later and is running on devices API 36 or
- * later, this function always returns true. Otherwise, this function returns true if
- * the given settings is effective to at least one font file underlying this typeface.
- * This function also returns true for empty settings string. Otherwise returns false.
+ * @return true if the given settings is effective to at least one font file underlying this
+ * typeface. This function also returns true for empty settings string. Otherwise
+ * returns false
*
* @throws IllegalArgumentException If given string is not a valid font variation settings
* format
@@ -2154,39 +2141,6 @@ public class Paint {
* @see FontVariationAxis
*/
public boolean setFontVariationSettings(String fontVariationSettings) {
- return setFontVariationSettings(fontVariationSettings, 0 /* wght adjust */);
- }
-
- /**
- * Set font variation settings with weight adjustment
- * @hide
- */
- public boolean setFontVariationSettings(String fontVariationSettings, int wghtAdjust) {
- final boolean useFontVariationStore = Flags.typefaceRedesignReadonly()
- && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
- if (useFontVariationStore) {
- FontVariationAxis[] axes =
- FontVariationAxis.fromFontVariationSettings(fontVariationSettings);
- if (axes == null) {
- nSetFontVariationOverride(mNativePaint, 0);
- mFontVariationSettings = null;
- return true;
- }
-
- long builderPtr = nCreateFontVariationBuilder(axes.length);
- for (int i = 0; i < axes.length; ++i) {
- int tag = axes[i].getOpenTypeTagValue();
- float value = axes[i].getStyleValue();
- if (tag == 0x77676874 /* wght */) {
- value = Math.clamp(value + wghtAdjust,
- FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX);
- }
- nAddFontVariationToBuilder(builderPtr, tag, value);
- }
- nSetFontVariationOverride(mNativePaint, builderPtr);
- mFontVariationSettings = fontVariationSettings;
- return true;
- }
final String settings = TextUtils.nullIfEmpty(fontVariationSettings);
if (settings == mFontVariationSettings
|| (settings != null && settings.equals(mFontVariationSettings))) {
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index d1fe2cdbcd77..30a248bb3e0e 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -23,6 +23,7 @@ import android.os.Build;
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
@@ -139,9 +140,19 @@ public final class FontVariationAxis {
*/
public static @Nullable FontVariationAxis[] fromFontVariationSettings(
@Nullable String settings) {
- if (settings == null || settings.isEmpty()) {
+ List<FontVariationAxis> result = fromFontVariationSettingsForList(settings);
+ if (result.isEmpty()) {
return null;
}
+ return result.toArray(new FontVariationAxis[0]);
+ }
+
+ /** @hide */
+ public static @NonNull List<FontVariationAxis> fromFontVariationSettingsForList(
+ @Nullable String settings) {
+ if (settings == null || settings.isEmpty()) {
+ return Collections.emptyList();
+ }
final ArrayList<FontVariationAxis> axisList = new ArrayList<>();
final int length = settings.length();
for (int i = 0; i < length; i++) {
@@ -172,9 +183,9 @@ public final class FontVariationAxis {
i = endOfValueString;
}
if (axisList.isEmpty()) {
- return null;
+ return Collections.emptyList();
}
- return axisList.toArray(new FontVariationAxis[0]);
+ return axisList;
}
/**
diff --git a/keystore/java/android/security/keystore/KeyStoreManager.java b/keystore/java/android/security/keystore/KeyStoreManager.java
index 740ccb53a691..13f1a72469c2 100644
--- a/keystore/java/android/security/keystore/KeyStoreManager.java
+++ b/keystore/java/android/security/keystore/KeyStoreManager.java
@@ -312,9 +312,11 @@ public final class KeyStoreManager {
* When passed into getSupplementaryAttestationInfo, getSupplementaryAttestationInfo returns the
* DER-encoded structure corresponding to the `Modules` schema described in the KeyMint HAL's
* KeyCreationResult.aidl. The SHA-256 hash of this encoded structure is what's included with
- * the tag in attestations.
+ * the tag in attestations. To ensure the returned encoded structure is the one attested to,
+ * clients should verify its SHA-256 hash matches the one in the attestation. Note that the
+ * returned structure can vary between boots.
*/
- // TODO(b/369375199): Replace with Tag.MODULE_HASH when flagging is removed.
+ // TODO(b/380020528): Replace with Tag.MODULE_HASH when KeyMint V4 is frozen.
public static final int MODULE_HASH = TagType.BYTES | 724;
/**
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/animation/SizeChangeAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
new file mode 100644
index 000000000000..5018fdb615da
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
@@ -0,0 +1,288 @@
+/*
+ * 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.animation;
+
+import static com.android.wm.shell.transition.DefaultSurfaceAnimator.setupValueAnimator;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+
+import java.util.function.Consumer;
+
+/**
+ * Animation implementation for size-changing window container animations. Ported from
+ * {@link com.android.server.wm.WindowChangeAnimationSpec}.
+ * <p>
+ * This animation behaves slightly differently depending on whether the window is growing
+ * or shrinking:
+ * <ul>
+ * <li>If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
+ * snapshot.
+ * <li>If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
+ * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
+ * place.
+ * </ul>
+ */
+public class SizeChangeAnimation {
+ private final Rect mTmpRect = new Rect();
+ final Transformation mTmpTransform = new Transformation();
+ final Matrix mTmpMatrix = new Matrix();
+ final float[] mTmpFloats = new float[9];
+ final float[] mTmpVecs = new float[4];
+
+ private final Animation mAnimation;
+ private final Animation mSnapshotAnim;
+
+ private final ValueAnimator mAnimator = ValueAnimator.ofFloat(0f, 1f);
+
+ /**
+ * The maximum of stretching applied to any surface during interpolation (since the animation
+ * is a combination of stretching/cropping/fading).
+ */
+ private static final float SCALE_FACTOR = 0.7f;
+
+ /**
+ * Since this animation is made of several sub-animations, we want to pre-arrange the
+ * sub-animations on a "virtual timeline" and then drive the overall progress in lock-step.
+ *
+ * To do this, we have a single value-animator which animates progress from 0-1 with an
+ * arbitrary duration and interpolator. Then we convert the progress to a frame in our virtual
+ * timeline to get the interpolated transforms.
+ *
+ * The APIs for arranging the sub-animations use integral frame numbers, so we need to pick
+ * an integral "duration" for our virtual timeline. That's what this constant specifies. It
+ * is effectively an animation "resolution" since it divides-up the 0-1 interpolation-space.
+ */
+ private static final int ANIMATION_RESOLUTION = 1000;
+
+ public SizeChangeAnimation(Rect startBounds, Rect endBounds) {
+ mAnimation = buildContainerAnimation(startBounds, endBounds);
+ mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds);
+ }
+
+ /**
+ * Initialize a size-change animation for a container leash.
+ */
+ public void initialize(SurfaceControl leash, SurfaceControl snapshot,
+ SurfaceControl.Transaction startT) {
+ startT.reparent(snapshot, leash);
+ startT.setPosition(snapshot, 0, 0);
+ startT.show(snapshot);
+ startT.show(leash);
+ apply(startT, leash, snapshot, 0.f);
+ }
+
+ /**
+ * Initialize a size-change animation for a view containing the leash surface(s).
+ *
+ * Note that this **will** apply {@param startToApply}!
+ */
+ public void initialize(View view, SurfaceControl leash, SurfaceControl snapshot,
+ SurfaceControl.Transaction startToApply) {
+ startToApply.reparent(snapshot, leash);
+ startToApply.setPosition(snapshot, 0, 0);
+ startToApply.show(snapshot);
+ startToApply.show(leash);
+ apply(view, startToApply, leash, snapshot, 0.f);
+ }
+
+ private ValueAnimator buildAnimatorInner(ValueAnimator.AnimatorUpdateListener updater,
+ SurfaceControl leash, SurfaceControl snapshot, Consumer<Animator> onFinish,
+ SurfaceControl.Transaction transaction, @Nullable View view) {
+ return setupValueAnimator(mAnimator, updater, (anim) -> {
+ transaction.reparent(snapshot, null);
+ if (view != null) {
+ view.setClipBounds(null);
+ view.setAnimationMatrix(null);
+ transaction.setCrop(leash, null);
+ }
+ transaction.apply();
+ transaction.close();
+ onFinish.accept(anim);
+ });
+ }
+
+ /**
+ * Build an animator which works on a pair of surface controls (where the snapshot is assumed
+ * to be a child of the main leash).
+ *
+ * @param onFinish Called when animation finishes. This is called on the anim thread!
+ */
+ public ValueAnimator buildAnimator(SurfaceControl leash, SurfaceControl snapshot,
+ Consumer<Animator> onFinish) {
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ Choreographer choreographer = Choreographer.getInstance();
+ return buildAnimatorInner(animator -> {
+ // The finish callback in buildSurfaceAnimation will ensure that the animation ends
+ // with fraction 1.
+ final float progress = Math.clamp(animator.getAnimatedFraction(), 0.f, 1.f);
+ apply(transaction, leash, snapshot, progress);
+ transaction.setFrameTimelineVsync(choreographer.getVsyncId());
+ transaction.apply();
+ }, leash, snapshot, onFinish, transaction, null /* view */);
+ }
+
+ /**
+ * Build an animator which works on a view that contains a pair of surface controls (where
+ * the snapshot is assumed to be a child of the main leash).
+ *
+ * @param onFinish Called when animation finishes. This is called on the anim thread!
+ */
+ public ValueAnimator buildViewAnimator(View view, SurfaceControl leash,
+ SurfaceControl snapshot, Consumer<Animator> onFinish) {
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ return buildAnimatorInner(animator -> {
+ // The finish callback in buildSurfaceAnimation will ensure that the animation ends
+ // with fraction 1.
+ final float progress = Math.clamp(animator.getAnimatedFraction(), 0.f, 1.f);
+ apply(view, transaction, leash, snapshot, progress);
+ }, leash, snapshot, onFinish, transaction, view);
+ }
+
+ /** Animation for the whole container (snapshot is inside this container). */
+ private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds) {
+ final long duration = ANIMATION_RESOLUTION;
+ boolean growing = endBounds.width() - startBounds.width()
+ + endBounds.height() - startBounds.height() >= 0;
+ long scalePeriod = (long) (duration * SCALE_FACTOR);
+ float startScaleX = SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width()
+ + (1.f - SCALE_FACTOR);
+ float startScaleY = SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height()
+ + (1.f - SCALE_FACTOR);
+ final AnimationSet animSet = new AnimationSet(true);
+
+ final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
+ scaleAnim.setDuration(scalePeriod);
+ if (!growing) {
+ scaleAnim.setStartOffset(duration - scalePeriod);
+ }
+ animSet.addAnimation(scaleAnim);
+ final Animation translateAnim = new TranslateAnimation(startBounds.left,
+ endBounds.left, startBounds.top, endBounds.top);
+ translateAnim.setDuration(duration);
+ animSet.addAnimation(translateAnim);
+ Rect startClip = new Rect(startBounds);
+ Rect endClip = new Rect(endBounds);
+ startClip.offsetTo(0, 0);
+ endClip.offsetTo(0, 0);
+ final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+ clipAnim.setDuration(duration);
+ animSet.addAnimation(clipAnim);
+ animSet.initialize(startBounds.width(), startBounds.height(),
+ endBounds.width(), endBounds.height());
+ return animSet;
+ }
+
+ /** The snapshot surface is assumed to be a child of the container surface. */
+ private static AnimationSet buildSnapshotAnimation(Rect startBounds, Rect endBounds) {
+ final long duration = ANIMATION_RESOLUTION;
+ boolean growing = endBounds.width() - startBounds.width()
+ + endBounds.height() - startBounds.height() >= 0;
+ long scalePeriod = (long) (duration * SCALE_FACTOR);
+ float endScaleX = 1.f / (SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width()
+ + (1.f - SCALE_FACTOR));
+ float endScaleY = 1.f / (SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height()
+ + (1.f - SCALE_FACTOR));
+
+ AnimationSet snapAnimSet = new AnimationSet(true);
+ // Animation for the "old-state" snapshot that is atop the task.
+ final Animation snapAlphaAnim = new AlphaAnimation(1.f, 0.f);
+ snapAlphaAnim.setDuration(scalePeriod);
+ if (!growing) {
+ snapAlphaAnim.setStartOffset(duration - scalePeriod);
+ }
+ snapAnimSet.addAnimation(snapAlphaAnim);
+ final Animation snapScaleAnim =
+ new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
+ snapScaleAnim.setDuration(duration);
+ snapAnimSet.addAnimation(snapScaleAnim);
+ snapAnimSet.initialize(startBounds.width(), startBounds.height(),
+ endBounds.width(), endBounds.height());
+ return snapAnimSet;
+ }
+
+ private void calcCurrentClipBounds(Rect outClip, Transformation fromTransform) {
+ // The following applies an inverse scale to the clip-rect so that it crops "after" the
+ // scale instead of before.
+ mTmpVecs[1] = mTmpVecs[2] = 0;
+ mTmpVecs[0] = mTmpVecs[3] = 1;
+ fromTransform.getMatrix().mapVectors(mTmpVecs);
+
+ mTmpVecs[0] = 1.f / mTmpVecs[0];
+ mTmpVecs[3] = 1.f / mTmpVecs[3];
+ final Rect clipRect = fromTransform.getClipRect();
+ outClip.left = (int) (clipRect.left * mTmpVecs[0] + 0.5f);
+ outClip.right = (int) (clipRect.right * mTmpVecs[0] + 0.5f);
+ outClip.top = (int) (clipRect.top * mTmpVecs[3] + 0.5f);
+ outClip.bottom = (int) (clipRect.bottom * mTmpVecs[3] + 0.5f);
+ }
+
+ private void apply(SurfaceControl.Transaction t, SurfaceControl leash, SurfaceControl snapshot,
+ float progress) {
+ long currentPlayTime = (long) (((float) ANIMATION_RESOLUTION) * progress);
+ // update thumbnail surface
+ mSnapshotAnim.getTransformation(currentPlayTime, mTmpTransform);
+ t.setMatrix(snapshot, mTmpTransform.getMatrix(), mTmpFloats);
+ t.setAlpha(snapshot, mTmpTransform.getAlpha());
+
+ // update container surface
+ mAnimation.getTransformation(currentPlayTime, mTmpTransform);
+ final Matrix matrix = mTmpTransform.getMatrix();
+ t.setMatrix(leash, matrix, mTmpFloats);
+
+ calcCurrentClipBounds(mTmpRect, mTmpTransform);
+ t.setCrop(leash, mTmpRect);
+ }
+
+ private void apply(View view, SurfaceControl.Transaction tmpT, SurfaceControl leash,
+ SurfaceControl snapshot, float progress) {
+ long currentPlayTime = (long) (((float) ANIMATION_RESOLUTION) * progress);
+ // update thumbnail surface
+ mSnapshotAnim.getTransformation(currentPlayTime, mTmpTransform);
+ tmpT.setMatrix(snapshot, mTmpTransform.getMatrix(), mTmpFloats);
+ tmpT.setAlpha(snapshot, mTmpTransform.getAlpha());
+
+ // update container surface
+ mAnimation.getTransformation(currentPlayTime, mTmpTransform);
+ final Matrix matrix = mTmpTransform.getMatrix();
+ mTmpMatrix.set(matrix);
+ // animationMatrix is applied after getTranslation, so "move" the translate to the end.
+ mTmpMatrix.preTranslate(-view.getTranslationX(), -view.getTranslationY());
+ mTmpMatrix.postTranslate(view.getTranslationX(), view.getTranslationY());
+ view.setAnimationMatrix(mTmpMatrix);
+
+ calcCurrentClipBounds(mTmpRect, mTmpTransform);
+ tmpT.setCrop(leash, mTmpRect);
+ view.setClipBounds(mTmpRect);
+
+ // this takes stuff out of mTmpT so mTmpT can be re-used immediately
+ view.getViewRootImpl().applyTransactionOnDraw(tmpT);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOut.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOut.java
new file mode 100644
index 000000000000..9451374befe0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOut.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.appzoomout;
+
+import com.android.wm.shell.shared.annotations.ExternalThread;
+
+/**
+ * Interface to engage with the app zoom out feature.
+ */
+@ExternalThread
+public interface AppZoomOut {
+
+ /**
+ * Called when the zoom out progress is updated, which is used to scale down the current app
+ * surface from fullscreen to the max pushback level we want to apply. {@param progress} ranges
+ * between [0,1], 0 when fullscreen, 1 when it's at the max pushback level.
+ */
+ void setProgress(float progress);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
new file mode 100644
index 000000000000..8cd7b0f48003
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.appzoomout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Slog;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayChangeController;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+/** Class that manages the app zoom out UI and states. */
+public class AppZoomOutController implements RemoteCallable<AppZoomOutController>,
+ ShellTaskOrganizer.FocusListener, DisplayChangeController.OnDisplayChangingListener {
+
+ private static final String TAG = "AppZoomOutController";
+
+ private final Context mContext;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final DisplayController mDisplayController;
+ private final AppZoomOutDisplayAreaOrganizer mDisplayAreaOrganizer;
+ private final ShellExecutor mMainExecutor;
+ private final AppZoomOutImpl mImpl = new AppZoomOutImpl();
+
+ private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ updateDisplayLayout(displayId);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ updateDisplayLayout(displayId);
+ }
+ };
+
+
+ public static AppZoomOutController create(Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController,
+ DisplayLayout displayLayout, @ShellMainThread ShellExecutor mainExecutor) {
+ AppZoomOutDisplayAreaOrganizer displayAreaOrganizer = new AppZoomOutDisplayAreaOrganizer(
+ context, displayLayout, mainExecutor);
+ return new AppZoomOutController(context, shellInit, shellTaskOrganizer, displayController,
+ displayAreaOrganizer, mainExecutor);
+ }
+
+ @VisibleForTesting
+ AppZoomOutController(Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController,
+ AppZoomOutDisplayAreaOrganizer displayAreaOrganizer,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mTaskOrganizer = shellTaskOrganizer;
+ mDisplayController = displayController;
+ mDisplayAreaOrganizer = displayAreaOrganizer;
+ mMainExecutor = mainExecutor;
+
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mTaskOrganizer.addFocusListener(this);
+
+ mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
+ mDisplayController.addDisplayChangingController(this);
+
+ mDisplayAreaOrganizer.registerOrganizer();
+ }
+
+ public AppZoomOut asAppZoomOut() {
+ return mImpl;
+ }
+
+ public void setProgress(float progress) {
+ mDisplayAreaOrganizer.setProgress(progress);
+ }
+
+ void updateDisplayLayout(int displayId) {
+ final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId);
+ if (newDisplayLayout == null) {
+ Slog.w(TAG, "Failed to get new DisplayLayout.");
+ return;
+ }
+ mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
+ }
+
+ @Override
+ public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo == null) {
+ return;
+ }
+ if (taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME) {
+ mDisplayAreaOrganizer.setIsHomeTaskFocused(taskInfo.isFocused);
+ }
+ }
+
+ @Override
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
+ // TODO: verify if there is synchronization issues.
+ mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ @ExternalThread
+ private class AppZoomOutImpl implements AppZoomOut {
+ @Override
+ public void setProgress(float progress) {
+ mMainExecutor.execute(() -> AppZoomOutController.this.setProgress(progress));
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutDisplayAreaOrganizer.java
new file mode 100644
index 000000000000..1c37461b2d2b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutDisplayAreaOrganizer.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.appzoomout;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/** Display area organizer that manages the app zoom out UI and states. */
+public class AppZoomOutDisplayAreaOrganizer extends DisplayAreaOrganizer {
+
+ private static final float PUSHBACK_SCALE_FOR_LAUNCHER = 0.05f;
+ private static final float PUSHBACK_SCALE_FOR_APP = 0.025f;
+ private static final float INVALID_PROGRESS = -1;
+
+ private final DisplayLayout mDisplayLayout = new DisplayLayout();
+ private final Context mContext;
+ private final float mCornerRadius;
+ private final Map<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap =
+ new ArrayMap<>();
+
+ private float mProgress = INVALID_PROGRESS;
+ // Denote whether the home task is focused, null when it's not yet initialized.
+ @Nullable private Boolean mIsHomeTaskFocused;
+
+ public AppZoomOutDisplayAreaOrganizer(Context context,
+ DisplayLayout displayLayout, Executor mainExecutor) {
+ super(mainExecutor);
+ mContext = context;
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+ setDisplayLayout(displayLayout);
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo, SurfaceControl leash) {
+ leash.setUnreleasedWarningCallSite(
+ "AppZoomOutDisplayAreaOrganizer.onDisplayAreaAppeared");
+ mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
+ }
+
+ @Override
+ public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
+ final SurfaceControl leash = mDisplayAreaTokenMap.get(displayAreaInfo.token);
+ if (leash != null) {
+ leash.release();
+ }
+ mDisplayAreaTokenMap.remove(displayAreaInfo.token);
+ }
+
+ public void registerOrganizer() {
+ final List<DisplayAreaAppearedInfo> displayAreaInfos = registerOrganizer(
+ AppZoomOutDisplayAreaOrganizer.FEATURE_APP_ZOOM_OUT);
+ for (int i = 0; i < displayAreaInfos.size(); i++) {
+ final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
+ onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
+ }
+ }
+
+ @Override
+ public void unregisterOrganizer() {
+ super.unregisterOrganizer();
+ reset();
+ }
+
+ void setProgress(float progress) {
+ if (mProgress == progress) {
+ return;
+ }
+
+ mProgress = progress;
+ apply();
+ }
+
+ void setIsHomeTaskFocused(boolean isHomeTaskFocused) {
+ if (mIsHomeTaskFocused != null && mIsHomeTaskFocused == isHomeTaskFocused) {
+ return;
+ }
+
+ mIsHomeTaskFocused = isHomeTaskFocused;
+ apply();
+ }
+
+ private void apply() {
+ if (mIsHomeTaskFocused == null || mProgress == INVALID_PROGRESS) {
+ return;
+ }
+
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ float scale = mProgress * (mIsHomeTaskFocused
+ ? PUSHBACK_SCALE_FOR_LAUNCHER : PUSHBACK_SCALE_FOR_APP);
+ mDisplayAreaTokenMap.forEach((token, leash) -> updateSurface(tx, leash, scale));
+ tx.apply();
+ }
+
+ void setDisplayLayout(DisplayLayout displayLayout) {
+ mDisplayLayout.set(displayLayout);
+ }
+
+ private void reset() {
+ setProgress(0);
+ mProgress = INVALID_PROGRESS;
+ mIsHomeTaskFocused = null;
+ }
+
+ private void updateSurface(SurfaceControl.Transaction tx, SurfaceControl leash, float scale) {
+ if (scale == 0) {
+ // Reset when scale is set back to 0.
+ tx
+ .setCrop(leash, null)
+ .setScale(leash, 1, 1)
+ .setPosition(leash, 0, 0)
+ .setCornerRadius(leash, 0);
+ return;
+ }
+
+ tx
+ // Rounded corner can only be applied if a crop is set.
+ .setCrop(leash, 0, 0, mDisplayLayout.width(), mDisplayLayout.height())
+ .setScale(leash, 1 - scale, 1 - scale)
+ .setPosition(leash, scale * mDisplayLayout.width() * 0.5f,
+ scale * mDisplayLayout.height() * 0.5f)
+ .setCornerRadius(leash, mCornerRadius * (1 - scale));
+ }
+
+ void onRotateDisplay(Context context, int toRotation) {
+ if (mDisplayLayout.rotation() == toRotation) {
+ return;
+ }
+ mDisplayLayout.rotateTo(context.getResources(), toRotation);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
index c493aadd57b0..151dc438702d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
@@ -20,6 +20,7 @@ import android.os.HandlerThread;
import androidx.annotation.Nullable;
+import com.android.wm.shell.appzoomout.AppZoomOut;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -112,4 +113,7 @@ public interface WMComponent {
*/
@WMSingleton
Optional<DesktopMode> getDesktopMode();
+
+ @WMSingleton
+ Optional<AppZoomOut> getAppZoomOut();
}
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 23a0f4adb6d2..ab3c33ec7e43 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
@@ -91,6 +91,7 @@ import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.freeform.FreeformComponents;
@@ -111,6 +112,8 @@ import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.appzoomout.AppZoomOut;
+import com.android.wm.shell.appzoomout.AppZoomOutController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -1031,6 +1034,38 @@ public abstract class WMShellBaseModule {
});
}
+ @WMSingleton
+ @Provides
+ static DesktopWallpaperActivityTokenProvider provideDesktopWallpaperActivityTokenProvider() {
+ return new DesktopWallpaperActivityTokenProvider();
+ }
+
+ @WMSingleton
+ @Provides
+ static Optional<DesktopWallpaperActivityTokenProvider>
+ provideOptionalDesktopWallpaperActivityTokenProvider(
+ Context context,
+ DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider) {
+ if (DesktopModeStatus.canEnterDesktopMode(context)) {
+ return Optional.of(desktopWallpaperActivityTokenProvider);
+ }
+ return Optional.empty();
+ }
+
+ //
+ // App zoom out (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<AppZoomOut> provideAppZoomOut(
+ Optional<AppZoomOutController> appZoomOutController) {
+ return appZoomOutController.map((controller) -> controller.asAppZoomOut());
+ }
+
+ @BindsOptionalOf
+ abstract AppZoomOutController optionalAppZoomOutController();
+
//
// Task Stack
//
@@ -1075,6 +1110,7 @@ public abstract class WMShellBaseModule {
Optional<RecentTasksController> recentTasksOptional,
Optional<RecentsTransitionHandler> recentsTransitionHandlerOptional,
Optional<OneHandedController> oneHandedControllerOptional,
+ Optional<AppZoomOutController> appZoomOutControllerOptional,
Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
Optional<ActivityEmbeddingController> activityEmbeddingOptional,
Optional<MixedTransitionHandler> mixedTransitionHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 1916215dea74..e8add56619c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -50,6 +50,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.apptoweb.AssistContentRequester;
+import com.android.wm.shell.appzoomout.AppZoomOutController;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -945,7 +946,8 @@ public abstract class WMShellModule {
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -961,7 +963,7 @@ public abstract class WMShellModule {
desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
- taskResourceLoader));
+ taskResourceLoader, recentsTransitionHandler));
}
@WMSingleton
@@ -1312,10 +1314,21 @@ public abstract class WMShellModule {
return new DesktopModeUiEventLogger(uiEventLogger, packageManager);
}
+ //
+ // App zoom out
+ //
+
@WMSingleton
@Provides
- static DesktopWallpaperActivityTokenProvider provideDesktopWallpaperActivityTokenProvider() {
- return new DesktopWallpaperActivityTokenProvider();
+ static AppZoomOutController provideAppZoomOutController(
+ Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ DisplayController displayController,
+ DisplayLayout displayLayout,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return AppZoomOutController.create(context, shellInit, shellTaskOrganizer,
+ displayController, displayLayout, mainExecutor);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 9e2b9b20be16..c8d0dab39837 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -41,6 +41,7 @@ import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
@@ -82,11 +83,14 @@ public abstract class Pip2Module {
@NonNull PipTransitionState pipStackListenerController,
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipUiStateChangeController pipUiStateChangeController,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional) {
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
- pipUiStateChangeController, desktopUserRepositoriesOptional);
+ pipUiStateChangeController, desktopUserRepositoriesOptional,
+ desktopWallpaperActivityTokenProviderOptional);
}
@WMSingleton
@@ -138,9 +142,12 @@ public abstract class Pip2Module {
@ShellMainThread ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
- desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer);
+ desktopUserRepositoriesOptional, desktopWallpaperActivityTokenProviderOptional,
+ rootTaskDisplayAreaOrganizer);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index d3066645f32e..1a58363dab81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -27,6 +27,7 @@ import androidx.core.util.forEach
import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -56,6 +57,10 @@ class DesktopRepository(
* @property topTransparentFullscreenTaskId the task id of any current top transparent
* fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is
* closed or sent to back. (top is at index 0).
+ * @property pipTaskId the task id of PiP task entered while in Desktop Mode.
+ * @property pipShouldKeepDesktopActive whether an active PiP window should keep the Desktop
+ * Mode session active. Only false when we are explicitly exiting Desktop Mode (via user
+ * action) while there is an active PiP window.
*/
private data class DesktopTaskData(
val activeTasks: ArraySet<Int> = ArraySet(),
@@ -66,6 +71,8 @@ class DesktopRepository(
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
var fullImmersiveTaskId: Int? = null,
var topTransparentFullscreenTaskId: Int? = null,
+ var pipTaskId: Int? = null,
+ var pipShouldKeepDesktopActive: Boolean = true,
) {
fun deepCopy(): DesktopTaskData =
DesktopTaskData(
@@ -76,6 +83,8 @@ class DesktopRepository(
freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
fullImmersiveTaskId = fullImmersiveTaskId,
topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
+ pipTaskId = pipTaskId,
+ pipShouldKeepDesktopActive = pipShouldKeepDesktopActive,
)
fun clear() {
@@ -86,6 +95,8 @@ class DesktopRepository(
freeformTasksInZOrder.clear()
fullImmersiveTaskId = null
topTransparentFullscreenTaskId = null
+ pipTaskId = null
+ pipShouldKeepDesktopActive = true
}
}
@@ -104,6 +115,9 @@ class DesktopRepository(
/* Tracks last bounds of task before toggled to immersive state. */
private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()
+ /* Callback for when a pending PiP transition has been aborted. */
+ private var onPipAbortedCallback: ((Int, Int) -> Unit)? = null
+
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
@@ -302,6 +316,54 @@ class DesktopRepository(
}
}
+ /** Set whether the given task is the Desktop-entered PiP task in this display. */
+ fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
+ val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId)
+ if (enterPip) {
+ desktopData.pipTaskId = taskId
+ desktopData.pipShouldKeepDesktopActive = true
+ } else {
+ desktopData.pipTaskId =
+ if (desktopData.pipTaskId == taskId) null
+ else {
+ logW(
+ "setTaskInPip: taskId=$taskId did not match saved taskId=${desktopData.pipTaskId}"
+ )
+ desktopData.pipTaskId
+ }
+ }
+ notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId))
+ }
+
+ /** Returns whether there is a PiP that was entered/minimized from Desktop in this display. */
+ fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean =
+ desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId != null
+
+ /** Returns whether the given task is the Desktop-entered PiP task in this display. */
+ fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
+ desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId == taskId
+
+ /** Returns whether Desktop session should be active in this display due to active PiP. */
+ fun shouldDesktopBeActiveForPip(displayId: Int): Boolean =
+ Flags.enableDesktopWindowingPip() &&
+ isMinimizedPipPresentInDisplay(displayId) &&
+ desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive
+
+ /** Saves whether a PiP window should keep Desktop session active in this display. */
+ fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) {
+ desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive = keepActive
+ }
+
+ /** Saves callback to handle a pending PiP transition being aborted. */
+ fun setOnPipAbortedCallback(callbackIfPipAborted: ((Int, Int) -> Unit)?) {
+ onPipAbortedCallback = callbackIfPipAborted
+ }
+
+ /** Invokes callback to handle a pending PiP transition with the given task id being aborted. */
+ fun onPipAborted(displayId: Int, pipTaskId: Int) {
+ onPipAbortedCallback?.invoke(displayId, pipTaskId)
+ }
+
/** Set whether the given task is the full-immersive task in this display. */
fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) {
val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId)
@@ -338,8 +400,12 @@ class DesktopRepository(
}
private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
+ val visibleAndPipTasksCount =
+ if (shouldDesktopBeActiveForPip(displayId)) visibleTasksCount + 1 else visibleTasksCount
visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
+ executor.execute {
+ listener.onTasksVisibilityChanged(displayId, visibleAndPipTasksCount)
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 050dfb6f562c..6013648c9806 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -225,7 +225,6 @@ class DesktopTasksController(
// Launch cookie used to identify a drag and drop transition to fullscreen after it has begun.
// Used to prevent handleRequest from moving the new fullscreen task to freeform.
private var dragAndDropFullscreenCookie: Binder? = null
- private var pendingPipTransitionAndTask: Pair<IBinder, Int>? = null
init {
desktopMode = DesktopModeImpl()
@@ -321,18 +320,29 @@ class DesktopTasksController(
fun visibleTaskCount(displayId: Int): Int = taskRepository.getVisibleTaskCount(displayId)
/**
- * Returns true if any freeform tasks are visible or if a transparent fullscreen task exists on
- * top in Desktop Mode.
+ * Returns true if any of the following is true:
+ * - Any freeform tasks are visible
+ * - A transparent fullscreen task exists on top in Desktop Mode
+ * - PiP on Desktop Windowing is enabled, there is an active PiP window and the desktop
+ * wallpaper is visible.
*/
fun isDesktopModeShowing(displayId: Int): Boolean {
+ val hasVisibleTasks = visibleTaskCount(displayId) > 0
+ val hasTopTransparentFullscreenTask =
+ taskRepository.getTopTransparentFullscreenTaskId(displayId) != null
+ val hasMinimizedPip =
+ Flags.enableDesktopWindowingPip() &&
+ taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
+ desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(displayId)
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
) {
- return visibleTaskCount(displayId) > 0 ||
- taskRepository.getTopTransparentFullscreenTaskId(displayId) != null
+ return hasVisibleTasks || hasTopTransparentFullscreenTask || hasMinimizedPip
+ } else if (Flags.enableDesktopWindowingPip()) {
+ return hasVisibleTasks || hasMinimizedPip
}
- return visibleTaskCount(displayId) > 0
+ return hasVisibleTasks
}
/** Moves focused task to desktop mode for given [displayId]. */
@@ -592,7 +602,7 @@ class DesktopTasksController(
): ((IBinder) -> Unit)? {
val taskId = taskInfo.taskId
desktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId)
- performDesktopExitCleanupIfNeeded(taskId, displayId, wct)
+ performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false)
taskRepository.addClosingTask(displayId, taskId)
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
doesAnyTaskRequireTaskbarRounding(displayId, taskId)
@@ -624,8 +634,12 @@ class DesktopTasksController(
)
val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
wct.merge(requestRes.second, true)
- pendingPipTransitionAndTask =
- freeformTaskTransitionStarter.startPipTransition(wct) to taskInfo.taskId
+ freeformTaskTransitionStarter.startPipTransition(wct)
+ taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
+ taskRepository.setOnPipAbortedCallback { displayId, taskId ->
+ minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!)
+ taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
+ }
return
}
@@ -636,7 +650,7 @@ class DesktopTasksController(
val taskId = taskInfo.taskId
val displayId = taskInfo.displayId
val wct = WindowContainerTransaction()
- performDesktopExitCleanupIfNeeded(taskId, displayId, wct)
+ performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false)
// Notify immersive handler as it might need to exit immersive state.
val exitResult =
desktopImmersiveController.exitImmersiveIfApplicable(
@@ -898,7 +912,12 @@ class DesktopTasksController(
}
if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
- performDesktopExitCleanupIfNeeded(task.taskId, task.displayId, wct)
+ performDesktopExitCleanupIfNeeded(
+ task.taskId,
+ task.displayId,
+ wct,
+ forceToFullscreen = false,
+ )
}
transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
@@ -1414,7 +1433,9 @@ class DesktopTasksController(
taskId: Int,
displayId: Int,
wct: WindowContainerTransaction,
+ forceToFullscreen: Boolean,
) {
+ taskRepository.setPipShouldKeepDesktopActive(displayId, !forceToFullscreen)
if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
if (!taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId)) {
return
@@ -1422,6 +1443,12 @@ class DesktopTasksController(
if (displayId != DEFAULT_DISPLAY) {
return
}
+ } else if (
+ Flags.enableDesktopWindowingPip() &&
+ taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
+ !forceToFullscreen
+ ) {
+ return
} else {
if (!taskRepository.isOnlyVisibleNonClosingTask(taskId)) {
return
@@ -1462,21 +1489,6 @@ class DesktopTasksController(
return false
}
- override fun onTransitionConsumed(
- transition: IBinder,
- aborted: Boolean,
- finishT: Transaction?,
- ) {
- pendingPipTransitionAndTask?.let { (pipTransition, taskId) ->
- if (transition == pipTransition) {
- if (aborted) {
- shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { minimizeTaskInner(it) }
- }
- pendingPipTransitionAndTask = null
- }
- }
- }
-
override fun handleRequest(
transition: IBinder,
request: TransitionRequestInfo,
@@ -1926,7 +1938,12 @@ class DesktopTasksController(
if (!isDesktopModeShowing(task.displayId)) return null
val wct = WindowContainerTransaction()
- performDesktopExitCleanupIfNeeded(task.taskId, task.displayId, wct)
+ performDesktopExitCleanupIfNeeded(
+ task.taskId,
+ task.displayId,
+ wct,
+ forceToFullscreen = false,
+ )
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
taskRepository.addClosingTask(task.displayId, task.taskId)
@@ -2053,7 +2070,12 @@ class DesktopTasksController(
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
- performDesktopExitCleanupIfNeeded(taskInfo.taskId, taskInfo.displayId, wct)
+ performDesktopExitCleanupIfNeeded(
+ taskInfo.taskId,
+ taskInfo.displayId,
+ wct,
+ forceToFullscreen = true,
+ )
}
private fun cascadeWindow(bounds: Rect, displayLayout: DisplayLayout, displayId: Int) {
@@ -2087,7 +2109,12 @@ class DesktopTasksController(
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
- performDesktopExitCleanupIfNeeded(taskInfo.taskId, taskInfo.displayId, wct)
+ performDesktopExitCleanupIfNeeded(
+ taskInfo.taskId,
+ taskInfo.displayId,
+ wct,
+ forceToFullscreen = false,
+ )
}
/** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 9bf5555fc194..d61ffdaf5cf8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -21,9 +21,11 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.os.IBinder
import android.view.SurfaceControl
-import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.DesktopModeFlags
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import android.window.TransitionInfo
@@ -39,6 +41,8 @@ import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
+import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
@@ -57,6 +61,8 @@ class DesktopTasksTransitionObserver(
) : Transitions.TransitionObserver {
private var transitionToCloseWallpaper: IBinder? = null
+ /* Pending PiP transition and its associated display id and task id. */
+ private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null
private var currentProfileId: Int
init {
@@ -90,6 +96,33 @@ class DesktopTasksTransitionObserver(
removeTaskIfNeeded(info)
}
removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
+
+ val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
+ info.changes.forEach { change ->
+ change.taskInfo?.let { taskInfo ->
+ if (
+ Flags.enableDesktopWindowingPip() &&
+ desktopRepository.isTaskMinimizedPipInDisplay(
+ taskInfo.displayId,
+ taskInfo.taskId,
+ )
+ ) {
+ when (info.type) {
+ TRANSIT_PIP ->
+ pendingPipTransitionAndPipTask =
+ Triple(transition, taskInfo.displayId, taskInfo.taskId)
+
+ TRANSIT_EXIT_PIP,
+ TRANSIT_REMOVE_PIP ->
+ desktopRepository.setTaskInPip(
+ taskInfo.displayId,
+ taskInfo.taskId,
+ enterPip = false,
+ )
+ }
+ }
+ }
+ }
}
private fun removeTaskIfNeeded(info: TransitionInfo) {
@@ -252,6 +285,18 @@ class DesktopTasksTransitionObserver(
}
}
transitionToCloseWallpaper = null
+ } else if (pendingPipTransitionAndPipTask?.first == transition) {
+ val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
+ if (aborted) {
+ pendingPipTransitionAndPipTask?.let {
+ desktopRepository.onPipAborted(
+ /*displayId=*/ it.second,
+ /* taskId=*/ it.third,
+ )
+ }
+ }
+ desktopRepository.setOnPipAbortedCallback(null)
+ pendingPipTransitionAndPipTask = null
}
}
@@ -263,11 +308,15 @@ class DesktopTasksTransitionObserver(
change.taskInfo?.let { taskInfo ->
if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
when (change.mode) {
- WindowManager.TRANSIT_OPEN -> {
+ TRANSIT_OPEN -> {
desktopWallpaperActivityTokenProvider.setToken(
taskInfo.token,
taskInfo.displayId,
)
+ desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
+ isVisible = true,
+ taskInfo.displayId,
+ )
// After the task for the wallpaper is created, set it non-trimmable.
// This is important to prevent recents from trimming and removing the
// task.
@@ -278,6 +327,16 @@ class DesktopTasksTransitionObserver(
}
TRANSIT_CLOSE ->
desktopWallpaperActivityTokenProvider.removeToken(taskInfo.displayId)
+ TRANSIT_TO_FRONT ->
+ desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
+ isVisible = true,
+ taskInfo.displayId,
+ )
+ TRANSIT_TO_BACK ->
+ desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
+ isVisible = false,
+ taskInfo.displayId,
+ )
else -> {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
index a87004c07d43..2bd7a9873a5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode.desktopwallpaperactivity
import android.util.SparseArray
+import android.util.SparseBooleanArray
import android.view.Display.DEFAULT_DISPLAY
import android.window.WindowContainerToken
@@ -24,6 +25,7 @@ import android.window.WindowContainerToken
class DesktopWallpaperActivityTokenProvider {
private val wallpaperActivityTokenByDisplayId = SparseArray<WindowContainerToken>()
+ private val wallpaperActivityVisByDisplayId = SparseBooleanArray()
fun setToken(token: WindowContainerToken, displayId: Int = DEFAULT_DISPLAY) {
wallpaperActivityTokenByDisplayId[displayId] = token
@@ -36,4 +38,16 @@ class DesktopWallpaperActivityTokenProvider {
fun removeToken(displayId: Int = DEFAULT_DISPLAY) {
wallpaperActivityTokenByDisplayId.delete(displayId)
}
+
+ fun setWallpaperActivityIsVisible(
+ isVisible: Boolean = false,
+ displayId: Int = DEFAULT_DISPLAY,
+ ) {
+ wallpaperActivityVisByDisplayId.put(displayId, isVisible)
+ }
+
+ fun isWallpaperActivityVisible(displayId: Int = DEFAULT_DISPLAY): Boolean {
+ return wallpaperActivityTokenByDisplayId[displayId] != null &&
+ wallpaperActivityVisByDisplayId.get(displayId, false)
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index a62dd1c83520..4df9ae4b2ee3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -107,8 +107,11 @@ public class DragUtils {
/**
* Returns a list of the mime types provided in the clip description.
*/
- public static String getMimeTypesConcatenated(ClipDescription description) {
+ public static String getMimeTypesConcatenated(@Nullable ClipDescription description) {
String mimeTypes = "";
+ if (description == null) {
+ return mimeTypes;
+ }
for (int i = 0; i < description.getMimeTypeCount(); i++) {
if (i > 0) {
mimeTypes += ", ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 7f673d2efc68..ea8dac982703 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -40,6 +40,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -59,6 +60,8 @@ public class PipScheduler {
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
+ private final Optional<DesktopWallpaperActivityTokenProvider>
+ mDesktopWallpaperActivityTokenProviderOptional;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private PipTransitionController mPipTransitionController;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
@@ -73,12 +76,16 @@ public class PipScheduler {
ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
+ mDesktopWallpaperActivityTokenProviderOptional =
+ desktopWallpaperActivityTokenProviderOptional;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mSurfaceControlTransactionFactory =
@@ -260,10 +267,18 @@ public class PipScheduler {
/** Returns whether PiP is exiting while we're in desktop mode. */
private boolean isPipExitingToDesktopMode() {
- return Flags.enableDesktopWindowingPip() && mDesktopUserRepositoriesOptional.isPresent()
- && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(
- Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId) > 0
- || isDisplayInFreeform());
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!Flags.enableDesktopWindowingPip() || mDesktopUserRepositoriesOptional.isEmpty()
+ || mDesktopWallpaperActivityTokenProviderOptional.isEmpty()) {
+ return false;
+ }
+ final int displayId = Objects.requireNonNull(
+ mPipTransitionState.getPipTaskInfo()).displayId;
+ return mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(displayId)
+ > 0
+ || mDesktopWallpaperActivityTokenProviderOptional.get().isWallpaperActivityVisible(
+ displayId)
+ || isDisplayInFreeform();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 8061ee9090b6..38015ca6d45f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -63,7 +63,9 @@ import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
@@ -110,6 +112,8 @@ public class PipTransition extends PipTransitionController implements
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
+ private final Optional<DesktopWallpaperActivityTokenProvider>
+ mDesktopWallpaperActivityTokenProviderOptional;
//
// Transition caches
@@ -145,7 +149,9 @@ public class PipTransition extends PipTransitionController implements
PipTransitionState pipTransitionState,
PipDisplayLayoutState pipDisplayLayoutState,
PipUiStateChangeController pipUiStateChangeController,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional) {
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -157,6 +163,8 @@ public class PipTransition extends PipTransitionController implements
mPipTransitionState.addPipTransitionStateChangedListener(this);
mPipDisplayLayoutState = pipDisplayLayoutState;
mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
+ mDesktopWallpaperActivityTokenProviderOptional =
+ desktopWallpaperActivityTokenProviderOptional;
}
@Override
@@ -826,13 +834,14 @@ public class PipTransition extends PipTransitionController implements
return false;
}
-
// Since opening a new task while in Desktop Mode always first open in Fullscreen
// until DesktopMode Shell code resolves it to Freeform, PipTransition will get a
// possibility to handle it also. In this case return false to not have it enter PiP.
final boolean isInDesktopSession = !mDesktopUserRepositoriesOptional.isEmpty()
- && mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(
- pipTask.displayId) > 0;
+ && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(
+ pipTask.displayId) > 0
+ || mDesktopUserRepositoriesOptional.get().getCurrent()
+ .isMinimizedPipPresentInDisplay(pipTask.displayId));
if (isInDesktopSession) {
return false;
}
@@ -968,6 +977,27 @@ public class PipTransition extends PipTransitionController implements
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
+ final TaskInfo pipTask = mPipTransitionState.getPipTaskInfo();
+ final boolean desktopPipEnabled = Flags.enableDesktopWindowingPip()
+ && mDesktopUserRepositoriesOptional.isPresent()
+ && mDesktopWallpaperActivityTokenProviderOptional.isPresent();
+ if (desktopPipEnabled && pipTask != null) {
+ final DesktopRepository desktopRepository =
+ mDesktopUserRepositoriesOptional.get().getCurrent();
+ final boolean wallpaperIsVisible =
+ mDesktopWallpaperActivityTokenProviderOptional.get()
+ .isWallpaperActivityVisible(pipTask.displayId);
+ if (desktopRepository.getVisibleTaskCount(pipTask.displayId) == 0
+ && wallpaperIsVisible) {
+ mTransitions.startTransition(
+ TRANSIT_TO_BACK,
+ new WindowContainerTransaction().reorder(
+ mDesktopWallpaperActivityTokenProviderOptional.get()
+ .getToken(pipTask.displayId), /* onTop= */ false),
+ null
+ );
+ }
+ }
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
index 32c79a2d02de..8cdb8c4512a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
@@ -17,9 +17,10 @@
package com.android.wm.shell.recents;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.RemoteAnimationTarget;
import android.window.TaskSnapshot;
-import android.os.Bundle;
+import android.window.TransitionInfo;
import com.android.wm.shell.recents.IRecentsAnimationController;
@@ -57,7 +58,8 @@ oneway interface IRecentsAnimationRunner {
*/
void onAnimationStart(in IRecentsAnimationController controller,
in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
- in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras) = 2;
+ in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras,
+ in TransitionInfo info) = 2;
/**
* Called when the task of an activity that has been started while the recents animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 76496b06a4dd..aeccd86e122c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -411,10 +411,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mInstanceId = System.identityHashCode(this);
mListener = listener;
mDeathHandler = () -> {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
- finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
- "deathRecipient");
+ mExecutor.execute(() -> {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
+ finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
+ "deathRecipient");
+ });
};
try {
mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -585,7 +587,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
new RemoteAnimationTarget[0],
- new Rect(0, 0, 0, 0), new Rect(), new Bundle());
+ new Rect(0, 0, 0, 0), new Rect(), new Bundle(),
+ null);
for (int i = 0; i < mStateListeners.size(); i++) {
mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
}
@@ -816,7 +819,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
- new Rect(0, 0, 0, 0), new Rect(), b);
+ new Rect(0, 0, 0, 0), new Rect(), b, info);
for (int i = 0; i < mStateListeners.size(); i++) {
mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
}
@@ -1273,6 +1276,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
"requested"));
}
+ /**
+ * @param runnerFinishCb The remote finish callback to run after finish is complete, this is
+ * not the same as mFinishCb which reports the transition is finished
+ * to WM.
+ */
private void finishInner(boolean toHome, boolean sendUserLeaveHint,
IResultReceiver runnerFinishCb, String reason) {
if (finishSyntheticTransition(runnerFinishCb, reason)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 5aa329108596..b6bd879c75eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2974,9 +2974,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int transitType = info.getType();
TransitionInfo.Change pipChange = null;
int closingSplitTaskId = -1;
- // This array tracks if we are sending stages TO_BACK in this transition.
- // TODO (b/349828130): Update for n apps
- boolean[] stagesSentToBack = new boolean[2];
+ // This array tracks where we are sending stages (TO_BACK/TO_FRONT) in this transition.
+ // TODO (b/349828130): Update for n apps (needs to handle different indices than 0/1).
+ // Also make sure having multiple changes per stage (2+ tasks in one stage) is being
+ // handled properly.
+ int[] stageChanges = new int[2];
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
@@ -3040,18 +3042,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ " with " + taskId + " before startAnimation().");
}
}
- if (isClosingType(change.getMode()) &&
- getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED) {
-
- // Record which stages are getting sent to back
- if (change.getMode() == TRANSIT_TO_BACK) {
- stagesSentToBack[getStageOfTask(taskId)] = true;
- }
+ final int stageOfTaskId = getStageOfTask(taskId);
+ if (stageOfTaskId == STAGE_TYPE_UNDEFINED) {
+ continue;
+ }
+ if (isClosingType(change.getMode())) {
// (For PiP transitions) If either one of the 2 stages is closing we're assuming
// we'll break split
closingSplitTaskId = taskId;
}
+ if (transitType == WindowManager.TRANSIT_WAKE) {
+ // Record which stages are receiving which changes
+ if ((change.getMode() == TRANSIT_TO_BACK
+ || change.getMode() == TRANSIT_TO_FRONT)
+ && (stageOfTaskId == STAGE_TYPE_MAIN
+ || stageOfTaskId == STAGE_TYPE_SIDE)) {
+ stageChanges[stageOfTaskId] = change.getMode();
+ }
+ }
}
if (pipChange != null) {
@@ -3076,19 +3085,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
- // If keyguard is active, check to see if we have our TO_BACK transitions in order.
- // This array should either be all false (no split stages sent to back) or all true
- // (all stages sent to back). In any other case (which can happen with SHOW_ABOVE_LOCKED
- // apps) we should break split.
- if (mKeyguardActive) {
- boolean isFirstStageSentToBack = stagesSentToBack[0];
- for (boolean b : stagesSentToBack) {
- // Compare each boolean to the first one. If any are different, break split.
- if (b != isFirstStageSentToBack) {
- dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- break;
- }
- }
+ // If keyguard is active, check to see if we have all our stages showing. If one stage
+ // was moved but not the other (which can happen with SHOW_ABOVE_LOCKED apps), we should
+ // break split.
+ if (mKeyguardActive && stageChanges[STAGE_TYPE_MAIN] != stageChanges[STAGE_TYPE_SIDE]) {
+ dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index d8884f6d8d38..f5aaaad93229 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -33,6 +33,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.TransactionPool;
import java.util.ArrayList;
+import java.util.function.Consumer;
public class DefaultSurfaceAnimator {
@@ -58,42 +59,12 @@ public class DefaultSurfaceAnimator {
// Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint());
- va.addUpdateListener(updateListener);
- va.addListener(new AnimatorListenerAdapter() {
- // It is possible for the end/cancel to be called more than once, which may cause
- // issues if the animating surface has already been released. Track the finished
- // state here to skip duplicate callbacks. See b/252872225.
- private boolean mFinished;
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onFinish();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- onFinish();
- }
-
- private void onFinish() {
- if (mFinished) return;
- mFinished = true;
- // Apply transformation of end state in case the animation is canceled.
- if (va.getAnimatedFraction() < 1f) {
- va.setCurrentFraction(1f);
- }
-
- pool.release(transaction);
- mainExecutor.execute(() -> {
- animations.remove(va);
- finishCallback.run();
- });
- // The update listener can continue to be called after the animation has ended if
- // end() is called manually again before the finisher removes the animation.
- // Remove it manually here to prevent animating a released surface.
- // See b/252872225.
- va.removeUpdateListener(updateListener);
- }
+ setupValueAnimator(va, updateListener, (vanim) -> {
+ pool.release(transaction);
+ mainExecutor.execute(() -> {
+ animations.remove(vanim);
+ finishCallback.run();
+ });
});
animations.add(va);
}
@@ -188,4 +159,50 @@ public class DefaultSurfaceAnimator {
}
}
}
+
+ /**
+ * Setup some callback logic on a value-animator. This helper ensures that a value animator
+ * finishes at its final fraction (1f) and that relevant callbacks are only called once.
+ */
+ public static ValueAnimator setupValueAnimator(ValueAnimator animator,
+ ValueAnimator.AnimatorUpdateListener updateListener,
+ Consumer<ValueAnimator> afterFinish) {
+ animator.addUpdateListener(updateListener);
+ animator.addListener(new AnimatorListenerAdapter() {
+ // It is possible for the end/cancel to be called more than once, which may cause
+ // issues if the animating surface has already been released. Track the finished
+ // state here to skip duplicate callbacks. See b/252872225.
+ private boolean mFinished;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onFinish();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ onFinish();
+ }
+
+ private void onFinish() {
+ if (mFinished) return;
+ mFinished = true;
+ // Apply transformation of end state in case the animation is canceled.
+ if (animator.getAnimatedFraction() < 1f) {
+ animator.setCurrentFraction(1f);
+ }
+ afterFinish.accept(animator);
+ // The update listener can continue to be called after the animation has ended if
+ // end() is called manually again before the finisher removes the animation.
+ // Remove it manually here to prevent animating a released surface.
+ // See b/252872225.
+ animator.removeUpdateListener(updateListener);
+ }
+ });
+ return animator;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 1689bb5778ae..36c3e9711f5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -55,6 +55,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static com.android.internal.policy.TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
@@ -69,6 +70,7 @@ import static com.android.wm.shell.transition.TransitionAnimationHelper.isCovere
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -104,6 +106,7 @@ import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.animation.SizeChangeAnimation;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -422,6 +425,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
ROTATION_ANIMATION_ROTATE, 0 /* flags */, animations, onAnimFinish);
continue;
}
+
+ if (Flags.portWindowSizeAnimation() && isTask
+ && TransitionInfo.isIndependent(change, info)
+ && change.getSnapshot() != null) {
+ startBoundsChangeAnimation(startTransaction, animations, change, onAnimFinish,
+ mMainExecutor);
+ continue;
+ }
}
// Hide the invisible surface directly without animating it if there is a display
@@ -734,6 +745,21 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
}
+ private void startBoundsChangeAnimation(@NonNull SurfaceControl.Transaction startT,
+ @NonNull ArrayList<Animator> animations, @NonNull TransitionInfo.Change change,
+ @NonNull Runnable finishCb, @NonNull ShellExecutor mainExecutor) {
+ final SizeChangeAnimation sca =
+ new SizeChangeAnimation(change.getStartAbsBounds(), change.getEndAbsBounds());
+ sca.initialize(change.getLeash(), change.getSnapshot(), startT);
+ final ValueAnimator va = sca.buildAnimator(change.getLeash(), change.getSnapshot(),
+ (animator) -> mainExecutor.execute(() -> {
+ animations.remove(animator);
+ finishCb.run();
+ }));
+ va.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+ animations.add(va);
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9fbda46bd2b7..429e0564dd2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -126,6 +126,8 @@ import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt;
import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -157,8 +159,10 @@ import kotlinx.coroutines.MainCoroutineDispatcher;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Supplier;
/**
@@ -247,6 +251,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final DesktopModeEventLogger mDesktopModeEventLogger;
private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
private final WindowDecorTaskResourceLoader mTaskResourceLoader;
+ private final RecentsTransitionHandler mRecentsTransitionHandler;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -282,7 +287,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader) {
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler) {
this(
context,
shellExecutor,
@@ -323,7 +329,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
focusTransitionObserver,
desktopModeEventLogger,
desktopModeUiEventLogger,
- taskResourceLoader);
+ taskResourceLoader,
+ recentsTransitionHandler);
}
@VisibleForTesting
@@ -367,7 +374,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader) {
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -436,6 +444,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDesktopModeEventLogger = desktopModeEventLogger;
mDesktopModeUiEventLogger = desktopModeUiEventLogger;
mTaskResourceLoader = taskResourceLoader;
+ mRecentsTransitionHandler = recentsTransitionHandler;
shellInit.addInitCallback(this::onInit, this);
}
@@ -450,6 +459,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
new DesktopModeOnTaskResizeAnimationListener());
mDesktopTasksController.setOnTaskRepositionAnimationListener(
new DesktopModeOnTaskRepositionAnimationListener());
+ if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
+ mRecentsTransitionHandler.addTransitionStateListener(
+ new DesktopModeRecentsTransitionStateListener());
+ }
mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
try {
mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
@@ -1859,6 +1872,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
}
+ private class DesktopModeRecentsTransitionStateListener
+ implements RecentsTransitionStateListener {
+ final Set<Integer> mAnimatingTaskIds = new HashSet<>();
+
+ @Override
+ public void onTransitionStateChanged(int state) {
+ switch (state) {
+ case RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED:
+ for (int n = 0; n < mWindowDecorByTaskId.size(); n++) {
+ int taskId = mWindowDecorByTaskId.keyAt(n);
+ mAnimatingTaskIds.add(taskId);
+ setIsRecentsTransitionRunningForTask(taskId, true);
+ }
+ return;
+ case RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING:
+ // No Recents transition running - clean up window decorations
+ for (int taskId : mAnimatingTaskIds) {
+ setIsRecentsTransitionRunningForTask(taskId, false);
+ }
+ mAnimatingTaskIds.clear();
+ return;
+ default:
+ }
+ }
+
+ private void setIsRecentsTransitionRunningForTask(int taskId, boolean isRecentsRunning) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.setIsRecentsTransitionRunning(isRecentsRunning);
+ }
+ }
+
private class DragEventListenerImpl
implements DragPositioningCallbackUtility.DragEventListener {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 4ac89546c9c7..39a989ce7c7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -204,6 +204,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final DesktopUserRepositories mDesktopUserRepositories;
+ private boolean mIsRecentsTransitionRunning = false;
private Runnable mLoadAppInfoRunnable;
private Runnable mSetAppInfoRunnable;
@@ -498,7 +499,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
- displayExclusionRegion);
+ displayExclusionRegion, mIsRecentsTransitionRunning);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -869,7 +870,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean inFullImmersiveMode,
@NonNull InsetsState displayInsetsState,
boolean hasGlobalFocus,
- @NonNull Region displayExclusionRegion) {
+ @NonNull Region displayExclusionRegion,
+ boolean shouldIgnoreCornerRadius) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -1006,13 +1008,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mWindowDecorConfig = windowDecorConfig;
if (DesktopModeStatus.useRoundedCorners()) {
- relayoutParams.mCornerRadius = taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- ? loadDimensionPixelSize(context.getResources(),
- R.dimen.desktop_windowing_freeform_rounded_corner_radius)
- : INVALID_CORNER_RADIUS;
+ relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS :
+ getCornerRadius(context, relayoutParams.mLayoutResId);
}
}
+ private static int getCornerRadius(@NonNull Context context, int layoutResId) {
+ if (layoutResId == R.layout.desktop_mode_app_header) {
+ return loadDimensionPixelSize(context.getResources(),
+ R.dimen.desktop_windowing_freeform_rounded_corner_radius);
+ }
+ return INVALID_CORNER_RADIUS;
+ }
+
/**
* If task has focused window decor, return the caption id of the fullscreen caption size
* resource. Otherwise, return ID_NULL and caption width be set to task width.
@@ -1740,6 +1748,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
+ * Declares whether a Recents transition is currently active.
+ *
+ * <p> When a Recents transition is active we allow that transition to take ownership of the
+ * corner radius of its task surfaces, so each window decoration should stop updating the corner
+ * radius of its task surface during that time.
+ */
+ void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) {
+ mIsRecentsTransitionRunning = isRecentsTransitionRunning;
+ }
+
+ /**
* Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
*/
void onMaximizeButtonHoverExit() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 5d1bedb85b5e..fa7183ad0fd8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -967,4 +967,4 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects), mFlags);
}
}
-} \ No newline at end of file
+}
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/appzoomout/AppZoomOutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/appzoomout/AppZoomOutControllerTest.java
new file mode 100644
index 000000000000..e91a1238a390
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/appzoomout/AppZoomOutControllerTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.appzoomout;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.testing.AndroidTestingRunner;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AppZoomOutControllerTest extends ShellTestCase {
+
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+ @Mock private DisplayController mDisplayController;
+ @Mock private AppZoomOutDisplayAreaOrganizer mDisplayAreaOrganizer;
+ @Mock private ShellExecutor mExecutor;
+ @Mock private ActivityManager.RunningTaskInfo mRunningTaskInfo;
+
+ private AppZoomOutController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Display display = mContext.getDisplay();
+ DisplayLayout displayLayout = new DisplayLayout(mContext, display);
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(displayLayout);
+
+ ShellInit shellInit = spy(new ShellInit(mExecutor));
+ mController = spy(new AppZoomOutController(mContext, shellInit, mTaskOrganizer,
+ mDisplayController, mDisplayAreaOrganizer, mExecutor));
+ }
+
+ @Test
+ public void isHomeTaskFocused_zoomOutForHome() {
+ mRunningTaskInfo.isFocused = true;
+ when(mRunningTaskInfo.getActivityType()).thenReturn(ACTIVITY_TYPE_HOME);
+ mController.onFocusTaskChanged(mRunningTaskInfo);
+
+ verify(mDisplayAreaOrganizer).setIsHomeTaskFocused(true);
+ }
+
+ @Test
+ public void isHomeTaskNotFocused_zoomOutForApp() {
+ mRunningTaskInfo.isFocused = false;
+ when(mRunningTaskInfo.getActivityType()).thenReturn(ACTIVITY_TYPE_HOME);
+ mController.onFocusTaskChanged(mRunningTaskInfo);
+
+ verify(mDisplayAreaOrganizer).setIsHomeTaskFocused(false);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index c0ff2f0652b3..9b24c1c06cec 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -52,6 +52,7 @@ import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.whenever
/** Tests for [DesktopModeEventLogger]. */
@@ -90,20 +91,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
val sessionId = desktopModeEventLogger.currentSessionId.get()
assertThat(sessionId).isNotEqualTo(NO_SESSION_ID)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
- /* event */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER),
- /* enter_reason */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER),
- /* exit_reason */
- eq(0),
- /* sessionId */
- eq(sessionId),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER,
+ 0,
+ sessionId,
+ )
verify {
EventLogTags.writeWmShellEnterDesktopMode(
eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason),
@@ -122,20 +115,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
val sessionId = desktopModeEventLogger.currentSessionId.get()
assertThat(sessionId).isNotEqualTo(NO_SESSION_ID)
assertThat(sessionId).isNotEqualTo(previousSessionId)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
- /* event */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER),
- /* enter_reason */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER),
- /* exit_reason */
- eq(0),
- /* sessionId */
- eq(sessionId),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER,
+ /* exit_reason */
+ 0,
+ sessionId,
+ )
verify {
EventLogTags.writeWmShellEnterDesktopMode(
eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason),
@@ -149,7 +135,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logSessionExit_noOngoingSession_doesNotLog() {
desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -159,20 +145,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
- /* event */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT),
- /* enter_reason */
- eq(0),
- /* exit_reason */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT),
- /* sessionId */
- eq(sessionId),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
+ /* enter_reason */
+ 0,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT,
+ sessionId,
+ )
verify {
EventLogTags.writeWmShellExitDesktopMode(
eq(ExitReason.DRAG_TO_EXIT.reason),
@@ -187,7 +166,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskAdded_noOngoingSession_doesNotLog() {
desktopModeEventLogger.logTaskAdded(TASK_UPDATE)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -197,32 +176,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskAdded(TASK_UPDATE)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
@@ -245,7 +211,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskRemoved_noOngoingSession_doesNotLog() {
desktopModeEventLogger.logTaskRemoved(TASK_UPDATE)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -255,32 +221,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskRemoved(TASK_UPDATE)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
@@ -303,7 +256,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskInfoChanged_noOngoingSession_doesNotLog() {
desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -313,35 +266,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
- ),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(
@@ -371,37 +308,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT)
)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
- ),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- /* minimize_reason */
- eq(MinimizeReason.TASK_LIMIT.reason),
- /* unminimize_reason */
- eq(UNSET_UNMINIMIZE_REASON),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ MinimizeReason.TASK_LIMIT.reason,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(
@@ -431,37 +350,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP)
)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
- ),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- /* minimize_reason */
- eq(UNSET_MINIMIZE_REASON),
- /* unminimize_reason */
- eq(UnminimizeReason.TASKBAR_TAP.reason),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UnminimizeReason.TASKBAR_TAP.reason,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(
@@ -491,7 +392,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
createTaskInfo(),
)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -509,39 +410,17 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
displayController,
)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
- /* resize_trigger */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
- ),
- /* resizing_stage */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE
- ),
- /* input_method */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
- ),
- /* desktop_mode_session_id */
- eq(sessionId),
- /* instance_id */
- eq(TASK_SIZE_UPDATE.instanceId),
- /* uid */
- eq(TASK_SIZE_UPDATE.uid),
- /* task_width */
- eq(TASK_SIZE_UPDATE.taskWidth),
- /* task_height */
- eq(TASK_SIZE_UPDATE.taskHeight),
- /* display_area */
- eq(DISPLAY_AREA),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskSizeUpdatedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD,
+ sessionId,
+ TASK_SIZE_UPDATE.instanceId,
+ TASK_SIZE_UPDATE.uid,
+ TASK_SIZE_UPDATE.taskWidth,
+ TASK_SIZE_UPDATE.taskHeight,
+ DISPLAY_AREA,
+ )
}
@Test
@@ -552,7 +431,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
createTaskInfo(),
)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -568,39 +447,17 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
displayController = displayController,
)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
- /* resize_trigger */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
- ),
- /* resizing_stage */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE
- ),
- /* input_method */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
- ),
- /* desktop_mode_session_id */
- eq(sessionId),
- /* instance_id */
- eq(TASK_SIZE_UPDATE.instanceId),
- /* uid */
- eq(TASK_SIZE_UPDATE.uid),
- /* task_width */
- eq(TASK_SIZE_UPDATE.taskWidth),
- /* task_height */
- eq(TASK_SIZE_UPDATE.taskHeight),
- /* display_area */
- eq(DISPLAY_AREA),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskSizeUpdatedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD,
+ sessionId,
+ TASK_SIZE_UPDATE.instanceId,
+ TASK_SIZE_UPDATE.uid,
+ TASK_SIZE_UPDATE.taskWidth,
+ TASK_SIZE_UPDATE.taskHeight,
+ DISPLAY_AREA,
+ )
}
private fun startDesktopModeSession(): Int {
@@ -652,6 +509,171 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
.build()
}
+ private fun verifyNoLogging() {
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ }
+
+ private fun verifyOnlyOneUiChangedLogging(
+ event: Int,
+ enterReason: Int,
+ exitReason: Int,
+ sessionId: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ eq(event),
+ eq(enterReason),
+ eq(exitReason),
+ eq(sessionId),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
+ private fun verifyOnlyOneTaskUpdateLogging(
+ taskEvent: Int,
+ instanceId: Int,
+ uid: Int,
+ taskHeight: Int,
+ taskWidth: Int,
+ taskX: Int,
+ taskY: Int,
+ sessionId: Int,
+ minimizeReason: Int,
+ unminimizeReason: Int,
+ visibleTaskCount: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ eq(taskEvent),
+ eq(instanceId),
+ eq(uid),
+ eq(taskHeight),
+ eq(taskWidth),
+ eq(taskX),
+ eq(taskY),
+ eq(sessionId),
+ eq(minimizeReason),
+ eq(unminimizeReason),
+ eq(visibleTaskCount),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
+ private fun verifyOnlyOneTaskSizeUpdatedLogging(
+ resizeTrigger: Int,
+ resizingStage: Int,
+ inputMethod: Int,
+ sessionId: Int,
+ instanceId: Int,
+ uid: Int,
+ taskWidth: Int,
+ taskHeight: Int,
+ displayArea: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ eq(resizeTrigger),
+ eq(resizingStage),
+ eq(inputMethod),
+ eq(sessionId),
+ eq(instanceId),
+ eq(uid),
+ eq(taskWidth),
+ eq(taskHeight),
+ eq(displayArea),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
private companion object {
private const val TASK_ID = 1
private const val TASK_UID = 1
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 5629127b8c54..daecccef9344 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -25,6 +25,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.INVALID_DISPLAY
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
@@ -1067,6 +1068,67 @@ class DesktopRepositoryTest : ShellTestCase() {
assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1)).isEqualTo(2)
}
+ @Test
+ fun setTaskInPip_savedAsMinimizedPipInDisplay() {
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse()
+
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
+ }
+
+ @Test
+ fun removeTaskInPip_removedAsMinimizedPipInDisplay() {
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
+
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false)
+
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse()
+ }
+
+ @Test
+ fun setTaskInPip_multipleDisplays_bothAreInPip() {
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID + 1, taskId = 2, enterPip = true)
+
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID + 1, taskId = 2)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun setPipShouldKeepDesktopActive_shouldKeepDesktopActive() {
+ assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
+
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+ repo.setPipShouldKeepDesktopActive(DEFAULT_DESKTOP_ID, keepActive = true)
+
+ assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun setPipShouldNotKeepDesktopActive_shouldNotKeepDesktopActive() {
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+ assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
+
+ repo.setPipShouldKeepDesktopActive(DEFAULT_DESKTOP_ID, keepActive = false)
+
+ assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun removeTaskInPip_shouldNotKeepDesktopActive() {
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+ assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
+
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false)
+
+ assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
+ }
+
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 692b50303038..4bb743079861 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -82,6 +82,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
@@ -569,6 +570,38 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun isDesktopModeShowing_minimizedPipTask_wallpaperVisible_returnsTrue() {
+ val pipTask = setUpPipTask(autoEnterEnabled = true)
+ whenever(desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible())
+ .thenReturn(true)
+
+ taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
+
+ assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun isDesktopModeShowing_minimizedPipTask_wallpaperNotVisible_returnsFalse() {
+ val pipTask = setUpPipTask(autoEnterEnabled = true)
+ whenever(desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible())
+ .thenReturn(false)
+
+ taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
+
+ assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun isDesktopModeShowing_pipTaskNotMinimizedNorVisible_returnsFalse() {
+ setUpPipTask(autoEnterEnabled = true)
+
+ assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
val homeTask = setUpHomeTask(SECOND_DISPLAY)
@@ -2039,6 +2072,41 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onDesktopWindowClose_minimizedPipPresent_doesNotExitDesktop() {
+ val freeformTask = setUpFreeformTask().apply { isFocused = true }
+ val pipTask = setUpPipTask(autoEnterEnabled = true)
+
+ taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask)
+
+ verifyExitDesktopWCTNotExecuted()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onDesktopWindowClose_minimizedPipNotPresent_exitDesktop() {
+ val freeformTask = setUpFreeformTask()
+ val pipTask = setUpPipTask(autoEnterEnabled = true)
+ val handler = mock(TransitionHandler::class.java)
+ whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
+ .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
+
+ controller.minimizeTask(pipTask)
+ verifyExitDesktopWCTNotExecuted()
+
+ taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false)
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask)
+
+ // Remove wallpaper operation
+ wct.hierarchyOps.any { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
+ }
+
+ @Test
fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = false)
val transition = Binder()
@@ -2055,10 +2123,9 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() {
+ fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() {
val task = setUpPipTask(autoEnterEnabled = true)
val handler = mock(TransitionHandler::class.java)
- whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder())
whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
.thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
@@ -2069,7 +2136,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() {
+ fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() {
val task = setUpPipTask(autoEnterEnabled = false)
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(Binder())
@@ -2081,6 +2148,22 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun onPipTaskMinimize_doesntRemoveWallpaper() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+ val handler = mock(TransitionHandler::class.java)
+ whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
+ .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
+
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
+ }
+
+ @Test
fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = true)
val transition = Binder()
@@ -3125,6 +3208,31 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun moveFocusedTaskToFullscreen_minimizedPipPresent_removeWallpaperActivity() {
+ val freeformTask = setUpFreeformTask()
+ val pipTask = setUpPipTask(autoEnterEnabled = true)
+ val handler = mock(TransitionHandler::class.java)
+ whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
+ .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
+
+ controller.minimizeTask(pipTask)
+ verifyExitDesktopWCTNotExecuted()
+
+ freeformTask.isFocused = true
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[freeformTask.token.asBinder()])
+ assertThat(taskChange.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Remove wallpaper operation
+ wct.hierarchyOps.any { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeDesktop_multipleTasks_removesAll() {
val task1 = setUpFreeformTask()
@@ -4851,7 +4959,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo {
- return setUpFreeformTask().apply {
+ // active = false marks the task as non-visible; PiP window doesn't count as visible tasks
+ return setUpFreeformTask(active = false).apply {
pictureInPictureParams =
PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 89ab65a42bbf..96ed214e7f88 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.os.Binder
import android.os.IBinder
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -29,7 +30,9 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IWindowContainerToken
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
@@ -38,6 +41,7 @@ import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
@@ -47,6 +51,8 @@ import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpape
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
+import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
@@ -300,6 +306,115 @@ class DesktopTasksTransitionObserverTest {
verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
}
+ @Test
+ fun transitOpenWallpaper_wallpaperActivityVisibilitySaved() {
+ val wallpaperTask = createWallpaperTaskInfo()
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createOpenChangeTransition(wallpaperTask),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(desktopWallpaperActivityTokenProvider)
+ .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId)
+ }
+
+ @Test
+ fun transitToFrontWallpaper_wallpaperActivityVisibilitySaved() {
+ val wallpaperTask = createWallpaperTaskInfo()
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createToFrontTransition(wallpaperTask),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(desktopWallpaperActivityTokenProvider)
+ .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId)
+ }
+
+ @Test
+ fun transitToBackWallpaper_wallpaperActivityVisibilitySaved() {
+ val wallpaperTask = createWallpaperTaskInfo()
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createToBackTransition(wallpaperTask),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(desktopWallpaperActivityTokenProvider)
+ .setWallpaperActivityIsVisible(isVisible = false, wallpaperTask.displayId)
+ }
+
+ @Test
+ fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() {
+ val wallpaperTask = createWallpaperTaskInfo()
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createCloseTransition(wallpaperTask),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(desktopWallpaperActivityTokenProvider).removeToken(wallpaperTask.displayId)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun pendingPipTransitionAborted_taskRepositoryOnPipAbortedInvoked() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ val pipTransition = Binder()
+ whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = pipTransition,
+ info = createOpenChangeTransition(task, TRANSIT_PIP),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+ transitionObserver.onTransitionFinished(transition = pipTransition, aborted = true)
+
+ verify(taskRepository).onPipAborted(task.displayId, task.taskId)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun exitPipTransition_taskRepositoryClearTaskInPip() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createOpenChangeTransition(task, type = TRANSIT_EXIT_PIP),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun removePipTransition_taskRepositoryClearTaskInPip() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createOpenChangeTransition(task, type = TRANSIT_REMOVE_PIP),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
@@ -331,7 +446,7 @@ class DesktopTasksTransitionObserverTest {
task: RunningTaskInfo?,
type: Int = TRANSIT_OPEN,
): TransitionInfo {
- return TransitionInfo(TRANSIT_OPEN, /* flags= */ 0).apply {
+ return TransitionInfo(type, /* flags= */ 0).apply {
addChange(
Change(mock(), mock()).apply {
mode = TRANSIT_OPEN
@@ -369,6 +484,19 @@ class DesktopTasksTransitionObserverTest {
}
}
+ private fun createToFrontTransition(task: RunningTaskInfo?): TransitionInfo {
+ return TransitionInfo(TRANSIT_TO_FRONT, 0 /* flags */).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_TO_FRONT
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+ }
+
private fun getLatestWct(
@WindowManager.TransitionType type: Int = TRANSIT_OPEN,
handlerClass: Class<out Transitions.TransitionHandler>? = null,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 32bb8bbdbbe3..1b1a5a909220 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -152,17 +152,16 @@ public class DragAndDropControllerTest extends ShellTestCase {
}
@Test
- public void testOnDragStarted_withNoClipData() {
+ public void testOnDragStarted_withNoClipDataOrDescription() {
final View dragLayout = mock(View.class);
final Display display = mock(Display.class);
doReturn(display).when(dragLayout).getDisplay();
doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
- final ClipData clipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);
final DragEvent event = mock(DragEvent.class);
doReturn(ACTION_DRAG_STARTED).when(event).getAction();
doReturn(null).when(event).getClipData();
- doReturn(clipData.getDescription()).when(event).getClipDescription();
+ doReturn(null).when(event).getClipDescription();
// Ensure there's a target so that onDrag will execute
mController.addDisplayDropTarget(0, mContext, mock(WindowManager.class),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index a8aa25700c7e..c42f6c35bcb0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -30,6 +30,7 @@ import static org.mockito.kotlin.MatchersKt.eq;
import static org.mockito.kotlin.VerificationKt.times;
import static org.mockito.kotlin.VerificationKt.verify;
+import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Matrix;
@@ -45,7 +46,9 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -56,6 +59,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -83,7 +87,8 @@ public class PipSchedulerTest {
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
@Mock private SurfaceControl.Transaction mMockTransaction;
@Mock private PipAlphaAnimator mMockAlphaAnimator;
- @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories;
+ @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
+ @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
@Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@@ -100,9 +105,13 @@ public class PipSchedulerTest {
when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
.thenReturn(mMockTransaction);
+ when(mMockDesktopUserRepositories.getCurrent())
+ .thenReturn(Mockito.mock(DesktopRepository.class));
+ when(mMockPipTransitionState.getPipTaskInfo()).thenReturn(Mockito.mock(TaskInfo.class));
mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
- mMockPipTransitionState, mMockOptionalDesktopUserRepositories,
+ mMockPipTransitionState, Optional.of(mMockDesktopUserRepositories),
+ Optional.of(mMockDesktopWallpaperActivityTokenProvider),
mRootTaskDisplayAreaOrganizer);
mPipScheduler.setPipTransitionController(mMockPipTransitionController);
mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 894d238b7e15..ab43119b14c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -169,7 +169,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
final IResultReceiver finishCallback = mock(IResultReceiver.class);
final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner);
- verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any(), any());
// Finish and verify no transition remains and that the provided finish callback is called
mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
@@ -184,7 +184,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner);
- verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any(), any());
mRecentsTransitionHandler.findController(transition).cancel("test");
mMainExecutor.flushAll();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index ffe8e7135513..79e9b9c8cd77 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -59,11 +59,12 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.window.flags.Flags
import com.android.wm.shell.R
-import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
+import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -539,7 +540,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
onLeftSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
+ .snapToHalfScreen(
+ eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
eq(ResizeTrigger.MAXIMIZE_BUTTON),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
eq(decor),
@@ -616,11 +618,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
onRightSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
+ .snapToHalfScreen(
+ eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
eq(ResizeTrigger.MAXIMIZE_BUTTON),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
eq(decor),
- )
+ )
}
@Test
@@ -1223,6 +1226,49 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
verify(task2, never()).onExclusionRegionChanged(newRegion)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_requestedState_setsTransitionRunning() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+
+ verify(decoration).setIsRecentsTransitionRunning(true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_nonRunningState_setsTransitionNotRunning() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING)
+
+ verify(decoration).setIsRecentsTransitionRunning(false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_requestedAndAnimating_setsTransitionRunningOnce() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING)
+
+ verify(decoration, times(1)).setIsRecentsTransitionRunning(true)
+ }
+
private fun createOpenTaskDecoration(
@WindowingMode windowingMode: Int,
taskSurface: SurfaceControl = SurfaceControl(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index b5e8cebc1277..8af8285d031c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -40,6 +40,7 @@ import android.view.SurfaceControl
import android.view.WindowInsets.Type.statusBars
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
@@ -65,6 +66,8 @@ import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
import com.android.wm.shell.desktopmode.education.AppHandleEducationController
import com.android.wm.shell.desktopmode.education.AppToWebEducationController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -151,6 +154,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockFocusTransitionObserver = mock<FocusTransitionObserver>()
protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>()
protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
+ protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
protected val motionEvent = mock<MotionEvent>()
val displayLayout = mock<DisplayLayout>()
protected lateinit var spyContext: TestableContext
@@ -164,6 +168,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected lateinit var mockitoSession: StaticMockitoSession
protected lateinit var shellInit: ShellInit
internal lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
+ protected lateinit var desktopModeRecentsTransitionStateListener: RecentsTransitionStateListener
protected lateinit var displayChangingListener:
DisplayChangeController.OnDisplayChangingListener
internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
@@ -219,7 +224,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockFocusTransitionObserver,
desktopModeEventLogger,
mock<DesktopModeUiEventLogger>(),
- mock<WindowDecorTaskResourceLoader>()
+ mock<WindowDecorTaskResourceLoader>(),
+ mockRecentsTransitionHandler,
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -256,6 +262,13 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
verify(displayInsetsController)
.addGlobalInsetsChangedListener(insetsChangedCaptor.capture())
desktopModeOnInsetsChangedListener = insetsChangedCaptor.firstValue
+ val recentsTransitionStateListenerCaptor = argumentCaptor<RecentsTransitionStateListener>()
+ if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
+ verify(mockRecentsTransitionHandler)
+ .addTransitionStateListener(recentsTransitionStateListenerCaptor.capture())
+ desktopModeRecentsTransitionStateListener =
+ recentsTransitionStateListenerCaptor.firstValue
+ }
val keyguardChangedCaptor =
argumentCaptor<DesktopModeKeyguardChangeListener>()
verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 6b02aeffd42a..9ea5fd6e1abe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -169,6 +169,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final boolean DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED = false;
private static final boolean DEFAULT_IS_IN_FULL_IMMERSIVE_MODE = false;
private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
+ private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -396,6 +397,31 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ fillRoundedCornersResources(/* fillValue= */ 30);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ DEFAULT_IS_STATUSBAR_VISIBLE,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ /* shouldIgnoreCornerRadius= */ true);
+
+ assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
public void updateRelayoutParams_appHeader_usesTaskDensity() {
final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
@@ -634,7 +660,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
insetsState,
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -659,7 +686,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -683,7 +711,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -707,7 +736,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -730,7 +760,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -753,7 +784,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -777,7 +809,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -793,7 +826,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -817,7 +851,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -1480,7 +1515,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
}
private DesktopModeWindowDecoration createWindowDecoration(
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index e693fcfd3918..dbb891455ddd 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -162,13 +162,10 @@ const std::string& ApkAssets::GetDebugName() const {
return assets_provider_->GetDebugName();
}
-UpToDate ApkAssets::IsUpToDate() const {
+bool ApkAssets::IsUpToDate() const {
// Loaders are invalidated by the app, not the system, so assume they are up to date.
- if (IsLoader()) {
- return UpToDate::Always;
- }
- const auto idmap_res = loaded_idmap_ ? loaded_idmap_->IsUpToDate() : UpToDate::Always;
- return combine(idmap_res, [this] { return assets_provider_->IsUpToDate(); });
+ return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate())
+ && assets_provider_->IsUpToDate());
}
} // namespace android
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 11b12eb030a6..2d3c06506a1f 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -24,8 +24,9 @@
#include <ziparchive/zip_archive.h>
namespace android {
-
-static constexpr std::string_view kEmptyDebugString = "<empty>";
+namespace {
+constexpr const char* kEmptyDebugString = "<empty>";
+} // namespace
std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
bool* file_exists) const {
@@ -85,9 +86,11 @@ void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const {
}
ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
- package_property_t flags, ModDate last_mod_time)
- : zip_handle_(handle), name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) {
-}
+ package_property_t flags, time_t last_mod_time)
+ : zip_handle_(handle),
+ name_(std::move(path)),
+ flags_(flags),
+ last_mod_time_(last_mod_time) {}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
package_property_t flags,
@@ -101,10 +104,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
return {};
}
- ModDate mod_date = kInvalidModDate;
+ struct stat sb{.st_mtime = -1};
// Skip all up-to-date checks if the file won't ever change.
- if (isKnownWritablePath(path.c_str()) || !isReadonlyFilesystem(GetFileDescriptor(handle))) {
- if (mod_date = getFileModDate(GetFileDescriptor(handle)); mod_date == kInvalidModDate) {
+ if (!isReadonlyFilesystem(path.c_str())) {
+ if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
@@ -113,7 +116,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
}
return std::unique_ptr<ZipAssetsProvider>(
- new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, mod_date));
+ new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime));
}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
@@ -134,10 +137,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
return {};
}
- ModDate mod_date = kInvalidModDate;
+ struct stat sb{.st_mtime = -1};
// Skip all up-to-date checks if the file won't ever change.
if (!isReadonlyFilesystem(released_fd)) {
- if (mod_date = getFileModDate(released_fd); mod_date == kInvalidModDate) {
+ if (fstat(released_fd, &sb) < 0) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
@@ -147,7 +150,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
}
return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider(
- handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, mod_date));
+ handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime));
}
std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
@@ -279,16 +282,21 @@ const std::string& ZipAssetsProvider::GetDebugName() const {
return name_.GetDebugName();
}
-UpToDate ZipAssetsProvider::IsUpToDate() const {
- if (last_mod_time_ == kInvalidModDate) {
- return UpToDate::Always;
+bool ZipAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == -1) {
+ return true;
+ }
+ struct stat sb{};
+ if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
+ // If fstat fails on the zip archive, return true so the zip archive the resource system does
+ // attempt to refresh the ApkAsset.
+ return true;
}
- return fromBool(last_mod_time_ == getFileModDate(GetFileDescriptor(zip_handle_.get())));
+ return last_mod_time_ == sb.st_mtime;
}
-DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time)
- : dir_(std::move(path)), last_mod_time_(last_mod_time) {
-}
+DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
+ : dir_(std::move(path)), last_mod_time_(last_mod_time) {}
std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
struct stat sb;
@@ -309,7 +317,7 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st
const bool isReadonly = isReadonlyFilesystem(path.c_str());
return std::unique_ptr<DirectoryAssetsProvider>(
- new DirectoryAssetsProvider(std::move(path), isReadonly ? kInvalidModDate : getModDate(sb)));
+ new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
}
std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -338,11 +346,17 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const {
return dir_;
}
-UpToDate DirectoryAssetsProvider::IsUpToDate() const {
- if (last_mod_time_ == kInvalidModDate) {
- return UpToDate::Always;
+bool DirectoryAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == -1) {
+ return true;
+ }
+ struct stat sb;
+ if (stat(dir_.c_str(), &sb) < 0) {
+ // If stat fails on the zip archive, return true so the zip archive the resource system does
+ // attempt to refresh the ApkAsset.
+ return true;
}
- return fromBool(last_mod_time_ == getFileModDate(dir_.c_str()));
+ return last_mod_time_ == sb.st_mtime;
}
MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
@@ -355,14 +369,8 @@ MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& prima
std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create(
std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) {
- if (primary == nullptr && secondary == nullptr) {
- return EmptyAssetsProvider::Create();
- }
- if (!primary) {
- return secondary;
- }
- if (!secondary) {
- return primary;
+ if (primary == nullptr || secondary == nullptr) {
+ return nullptr;
}
return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary),
std::move(secondary)));
@@ -389,8 +397,8 @@ const std::string& MultiAssetsProvider::GetDebugName() const {
return debug_name_;
}
-UpToDate MultiAssetsProvider::IsUpToDate() const {
- return combine(primary_->IsUpToDate(), [this] { return secondary_->IsUpToDate(); });
+bool MultiAssetsProvider::IsUpToDate() const {
+ return primary_->IsUpToDate() && secondary_->IsUpToDate();
}
EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) :
@@ -430,12 +438,12 @@ const std::string& EmptyAssetsProvider::GetDebugName() const {
if (path_.has_value()) {
return *path_;
}
- constexpr static std::string kEmpty{kEmptyDebugString};
+ const static std::string kEmpty = kEmptyDebugString;
return kEmpty;
}
-UpToDate EmptyAssetsProvider::IsUpToDate() const {
- return UpToDate::Always;
+bool EmptyAssetsProvider::IsUpToDate() const {
+ return true;
}
} // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 262e7df185b7..3ecd82b074a1 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -22,10 +22,9 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/utf8.h"
-#include "androidfw/AssetManager.h"
+#include "androidfw/misc.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
-#include "androidfw/misc.h"
#include "utils/ByteOrder.h"
#include "utils/Trace.h"
@@ -269,16 +268,11 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head
configurations_(configs),
overlay_entries_(overlay_entries),
string_pool_(std::move(string_pool)),
+ idmap_fd_(
+ android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)),
overlay_apk_path_(overlay_apk_path),
target_apk_path_(target_apk_path),
- idmap_last_mod_time_(kInvalidModDate) {
- if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) ||
- !(target_apk_path_ == AssetManager::TARGET_APK_PATH ||
- isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) {
- idmap_fd_.reset(
- android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH));
- idmap_last_mod_time_ = getFileModDate(idmap_fd_);
- }
+ idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {
}
std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
@@ -387,11 +381,8 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
}
-UpToDate LoadedIdmap::IsUpToDate() const {
- if (idmap_last_mod_time_ == kInvalidModDate) {
- return UpToDate::Always;
- }
- return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()));
+bool LoadedIdmap::IsUpToDate() const {
+ return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get());
}
} // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index a8eb062a2ece..de9991a8be5e 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -152,11 +152,12 @@ static void fill9patchOffsets(Res_png_9patch* patch) {
patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
}
-void Res_value::copyFrom_dtoh_slow(const Res_value& src) {
- size = dtohs(src.size);
- res0 = src.res0;
- dataType = src.dataType;
- data = dtohl(src.data);
+void Res_value::copyFrom_dtoh(const Res_value& src)
+{
+ size = dtohs(src.size);
+ res0 = src.res0;
+ dataType = src.dataType;
+ data = dtohl(src.data);
}
void Res_png_9patch::deviceToFile()
@@ -2030,6 +2031,16 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const
// --------------------------------------------------------------------
// --------------------------------------------------------------------
+void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
+ const size_t size = dtohl(o.size);
+ if (size >= sizeof(ResTable_config)) {
+ *this = o;
+ } else {
+ memcpy(this, &o, size);
+ memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
+ }
+}
+
/* static */ size_t unpackLanguageOrRegion(const char in[2], const char base,
char out[4]) {
if (in[0] & 0x80) {
@@ -2094,33 +2105,34 @@ size_t ResTable_config::unpackRegion(char region[4]) const {
return unpackLanguageOrRegion(this->country, '0', region);
}
-void ResTable_config::copyFromDtoH_slow(const ResTable_config& o) {
- copyFromDeviceNoSwap(o);
- size = sizeof(ResTable_config);
- mcc = dtohs(mcc);
- mnc = dtohs(mnc);
- density = dtohs(density);
- screenWidth = dtohs(screenWidth);
- screenHeight = dtohs(screenHeight);
- sdkVersion = dtohs(sdkVersion);
- minorVersion = dtohs(minorVersion);
- smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
- screenWidthDp = dtohs(screenWidthDp);
- screenHeightDp = dtohs(screenHeightDp);
-}
-
-void ResTable_config::swapHtoD_slow() {
- size = htodl(size);
- mcc = htods(mcc);
- mnc = htods(mnc);
- density = htods(density);
- screenWidth = htods(screenWidth);
- screenHeight = htods(screenHeight);
- sdkVersion = htods(sdkVersion);
- minorVersion = htods(minorVersion);
- smallestScreenWidthDp = htods(smallestScreenWidthDp);
- screenWidthDp = htods(screenWidthDp);
- screenHeightDp = htods(screenHeightDp);
+
+void ResTable_config::copyFromDtoH(const ResTable_config& o) {
+ copyFromDeviceNoSwap(o);
+ size = sizeof(ResTable_config);
+ mcc = dtohs(mcc);
+ mnc = dtohs(mnc);
+ density = dtohs(density);
+ screenWidth = dtohs(screenWidth);
+ screenHeight = dtohs(screenHeight);
+ sdkVersion = dtohs(sdkVersion);
+ minorVersion = dtohs(minorVersion);
+ smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
+ screenWidthDp = dtohs(screenWidthDp);
+ screenHeightDp = dtohs(screenHeightDp);
+}
+
+void ResTable_config::swapHtoD() {
+ size = htodl(size);
+ mcc = htods(mcc);
+ mnc = htods(mnc);
+ density = htods(density);
+ screenWidth = htods(screenWidth);
+ screenHeight = htods(screenHeight);
+ sdkVersion = htods(sdkVersion);
+ minorVersion = htods(minorVersion);
+ smallestScreenWidthDp = htods(smallestScreenWidthDp);
+ screenWidthDp = htods(screenWidthDp);
+ screenHeightDp = htods(screenHeightDp);
}
/* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) {
@@ -2133,7 +2145,7 @@ void ResTable_config::swapHtoD_slow() {
// systems should happen very infrequently (if at all.)
// The comparison code relies on memcmp low-level optimizations that make it
// more efficient than strncmp.
- static constexpr char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
+ const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript;
const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 86c459fb4647..be55fe8b4bb6 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -32,18 +32,13 @@ namespace android {
namespace util {
void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) {
- static constexpr bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
- if constexpr (kDeviceEndiannessSame) {
- *out = Utf16ToUtf8({(const char16_t*)src, strnlen16((const char16_t*)src, len)});
- } else {
- char buf[5];
- while (*src && len != 0) {
- char16_t c = static_cast<char16_t>(dtohs(*src));
- utf16_to_utf8(&c, 1, buf, sizeof(buf));
- out->append(buf, strlen(buf));
- ++src;
- --len;
- }
+ char buf[5];
+ while (*src && len != 0) {
+ char16_t c = static_cast<char16_t>(dtohs(*src));
+ utf16_to_utf8(&c, 1, buf, sizeof(buf));
+ out->append(buf, strlen(buf));
+ ++src;
+ --len;
}
}
@@ -68,10 +63,8 @@ std::string Utf16ToUtf8(StringPiece16 utf16) {
}
std::string utf8;
- utf8.resize_and_overwrite(utf8_length, [&utf16](char* data, size_t size) {
- utf16_to_utf8(utf16.data(), utf16.length(), data, size + 1);
- return size;
- });
+ utf8.resize(utf8_length);
+ utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
return utf8;
}
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
index a1385f2cf7b1..f7f62c51a25b 100644
--- a/libs/androidfw/ZipUtils.cpp
+++ b/libs/androidfw/ZipUtils.cpp
@@ -87,19 +87,29 @@ class BufferReader final : public zip_archive::Reader {
}
bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const override {
- if (mInputSize < len || offset > mInputSize - len) {
- return false;
- }
-
- const incfs::map_ptr<uint8_t> pos = mInput.offset(offset);
- if (!pos.verify(len)) {
+ auto in = AccessAtOffset(buf, len, offset);
+ if (!in) {
return false;
}
-
- memcpy(buf, pos.unsafe_ptr(), len);
+ memcpy(buf, in, len);
return true;
}
+ const uint8_t* AccessAtOffset(uint8_t*, size_t len, off64_t offset) const override {
+ if (offset > mInputSize - len) {
+ return nullptr;
+ }
+ const incfs::map_ptr<uint8_t> pos = mInput.offset(offset);
+ if (!pos.verify(len)) {
+ return nullptr;
+ }
+ return pos.unsafe_ptr();
+ }
+
+ bool IsZeroCopy() const override {
+ return true;
+ }
+
private:
const incfs::map_ptr<uint8_t> mInput;
const size_t mInputSize;
@@ -107,7 +117,7 @@ class BufferReader final : public zip_archive::Reader {
class BufferWriter final : public zip_archive::Writer {
public:
- BufferWriter(void* output, size_t outputSize) : Writer(),
+ BufferWriter(void* output, size_t outputSize) :
mOutput(reinterpret_cast<uint8_t*>(output)), mOutputSize(outputSize), mBytesWritten(0) {
}
@@ -121,6 +131,12 @@ class BufferWriter final : public zip_archive::Writer {
return true;
}
+ Buffer GetBuffer(size_t length) override {
+ const auto remaining_size = mOutputSize - mBytesWritten;
+ return remaining_size >= length
+ ? Buffer(mOutput + mBytesWritten, remaining_size) : Buffer();
+ }
+
private:
uint8_t* const mOutput;
const size_t mOutputSize;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 3f6f4661f2f7..231808beb718 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -116,7 +116,7 @@ class ApkAssets : public RefBase {
return resources_asset_ != nullptr && resources_asset_->isAllocated();
}
- UpToDate IsUpToDate() const;
+ bool IsUpToDate() const;
// DANGER!
// This is a destructive method that rips the assets provider out of ApkAssets object.
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index e3b3ae41f7f4..d33c325ff369 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#pragma once
+#ifndef ANDROIDFW_ASSETSPROVIDER_H
+#define ANDROIDFW_ASSETSPROVIDER_H
#include <memory>
#include <string>
@@ -57,7 +58,7 @@ struct AssetsProvider {
WARN_UNUSED virtual const std::string& GetDebugName() const = 0;
// Returns whether the interface provides the most recent version of its files.
- WARN_UNUSED virtual UpToDate IsUpToDate() const = 0;
+ WARN_UNUSED virtual bool IsUpToDate() const = 0;
// Creates an Asset from a file on disk.
static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
@@ -94,7 +95,7 @@ struct ZipAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED UpToDate IsUpToDate() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
~ZipAssetsProvider() override = default;
@@ -105,7 +106,7 @@ struct ZipAssetsProvider : public AssetsProvider {
private:
struct PathOrDebugName;
ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, package_property_t flags,
- ModDate last_mod_time);
+ time_t last_mod_time);
struct PathOrDebugName {
static PathOrDebugName Path(std::string value) {
@@ -134,7 +135,7 @@ struct ZipAssetsProvider : public AssetsProvider {
std::unique_ptr<ZipArchive, ZipCloser> zip_handle_;
PathOrDebugName name_;
package_property_t flags_;
- ModDate last_mod_time_;
+ time_t last_mod_time_;
};
// Supplies assets from a root directory.
@@ -146,7 +147,7 @@ struct DirectoryAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED UpToDate IsUpToDate() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
~DirectoryAssetsProvider() override = default;
protected:
@@ -155,23 +156,23 @@ struct DirectoryAssetsProvider : public AssetsProvider {
bool* file_exists) const override;
private:
- explicit DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time);
+ explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time);
std::string dir_;
- ModDate last_mod_time_;
+ time_t last_mod_time_;
};
// Supplies assets from a `primary` asset provider and falls back to supplying assets from the
// `secondary` asset provider if the asset cannot be found in the `primary`.
struct MultiAssetsProvider : public AssetsProvider {
static std::unique_ptr<AssetsProvider> Create(std::unique_ptr<AssetsProvider>&& primary,
- std::unique_ptr<AssetsProvider>&& secondary = {});
+ std::unique_ptr<AssetsProvider>&& secondary);
bool ForEachFile(const std::string& root_path,
base::function_ref<void(StringPiece, FileType)> f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED UpToDate IsUpToDate() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
~MultiAssetsProvider() override = default;
protected:
@@ -198,7 +199,7 @@ struct EmptyAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED UpToDate IsUpToDate() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
~EmptyAssetsProvider() override = default;
protected:
@@ -211,3 +212,5 @@ struct EmptyAssetsProvider : public AssetsProvider {
};
} // namespace android
+
+#endif /* ANDROIDFW_ASSETSPROVIDER_H */
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 87f3c9df9a91..ac75eb3bb98c 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#pragma once
+#ifndef IDMAP_H_
+#define IDMAP_H_
#include <memory>
#include <string>
@@ -31,31 +32,6 @@
namespace android {
-// An enum that tracks more states than just 'up to date' or 'not' for a resources container:
-// there are several cases where we know for sure that the object can't change and won't get
-// out of date. Reporting those states to the managed layer allows it to stop checking here
-// completely, speeding up the cache lookups by dozens of milliseconds.
-enum class UpToDate : int { False, True, Always };
-
-// Combines two UpToDate values, and only accesses the second one if it matters to the result.
-template <class Getter>
-UpToDate combine(UpToDate first, Getter secondGetter) {
- switch (first) {
- case UpToDate::False:
- return UpToDate::False;
- case UpToDate::True: {
- const auto second = secondGetter();
- return second == UpToDate::False ? UpToDate::False : UpToDate::True;
- }
- case UpToDate::Always:
- return secondGetter();
- }
-}
-
-inline UpToDate fromBool(bool value) {
- return value ? UpToDate::True : UpToDate::False;
-}
-
class LoadedIdmap;
class IdmapResMap;
struct Idmap_header;
@@ -220,7 +196,7 @@ class LoadedIdmap {
// Returns whether the idmap file on disk has not been modified since the construction of this
// LoadedIdmap.
- UpToDate IsUpToDate() const;
+ bool IsUpToDate() const;
protected:
// Exposed as protected so that tests can subclass and mock this class out.
@@ -255,3 +231,5 @@ class LoadedIdmap {
};
} // namespace android
+
+#endif // IDMAP_H_
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 819fe4b38c87..e330410ed1a0 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -47,8 +47,6 @@
namespace android {
-constexpr const bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
-
constexpr const uint32_t kIdmapMagic = 0x504D4449u;
constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au;
@@ -410,16 +408,7 @@ struct Res_value
typedef uint32_t data_type;
data_type data;
- void copyFrom_dtoh(const Res_value& src) {
- if constexpr (kDeviceEndiannessSame) {
- *this = src;
- } else {
- copyFrom_dtoh_slow(src);
- }
- }
-
- private:
- void copyFrom_dtoh_slow(const Res_value& src);
+ void copyFrom_dtoh(const Res_value& src);
};
/**
@@ -1265,32 +1254,11 @@ struct ResTable_config
// Varies in length from 3 to 8 chars. Zero-filled value.
char localeNumberingSystem[8];
- void copyFromDeviceNoSwap(const ResTable_config& o) {
- const auto o_size = dtohl(o.size);
- if (o_size >= sizeof(ResTable_config)) [[likely]] {
- *this = o;
- } else {
- memcpy(this, &o, o_size);
- memset(((uint8_t*)this) + o_size, 0, sizeof(ResTable_config) - o_size);
- }
- this->size = sizeof(*this);
- }
-
- void copyFromDtoH(const ResTable_config& o) {
- if constexpr (kDeviceEndiannessSame) {
- copyFromDeviceNoSwap(o);
- } else {
- copyFromDtoH_slow(o);
- }
- }
-
- void swapHtoD() {
- if constexpr (kDeviceEndiannessSame) {
- ; // noop
- } else {
- swapHtoD_slow();
- }
- }
+ void copyFromDeviceNoSwap(const ResTable_config& o);
+
+ void copyFromDtoH(const ResTable_config& o);
+
+ void swapHtoD();
int compare(const ResTable_config& o) const;
int compareLogical(const ResTable_config& o) const;
@@ -1416,10 +1384,6 @@ struct ResTable_config
bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
String8 toString() const;
-
- private:
- void copyFromDtoH_slow(const ResTable_config& o);
- void swapHtoD_slow();
};
/**
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index d8ca64a174a2..c9ba8a01a5e9 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -15,7 +15,6 @@
*/
#pragma once
-#include <sys/stat.h>
#include <time.h>
//
@@ -65,15 +64,10 @@ ModDate getFileModDate(const char* fileName);
/* same, but also returns -1 if the file has already been deleted */
ModDate getFileModDate(int fd);
-// Extract the modification date from the stat structure.
-ModDate getModDate(const struct ::stat& st);
-
// Check if |path| or |fd| resides on a readonly filesystem.
bool isReadonlyFilesystem(const char* path);
bool isReadonlyFilesystem(int fd);
-bool isKnownWritablePath(const char* path);
-
} // namespace android
// Whoever uses getFileModDate() will need this as well
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 26eb320805c9..32f3624a3aee 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -16,10 +16,10 @@
#define LOG_TAG "misc"
-#include "androidfw/misc.h"
-
-#include <errno.h>
-#include <sys/stat.h>
+//
+// Miscellaneous utility functions.
+//
+#include <androidfw/misc.h>
#include "android-base/logging.h"
@@ -28,7 +28,9 @@
#include <sys/vfs.h>
#endif // __linux__
-#include <array>
+#include <errno.h>
+#include <sys/stat.h>
+
#include <cstdio>
#include <cstring>
#include <tuple>
@@ -38,26 +40,28 @@ namespace android {
/*
* Get a file's type.
*/
-FileType getFileType(const char* fileName) {
- struct stat sb;
- if (stat(fileName, &sb) < 0) {
- if (errno == ENOENT || errno == ENOTDIR)
- return kFileTypeNonexistent;
- else {
- PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
- return kFileTypeUnknown;
- }
- } else {
- if (S_ISREG(sb.st_mode))
- return kFileTypeRegular;
- else if (S_ISDIR(sb.st_mode))
- return kFileTypeDirectory;
- else if (S_ISCHR(sb.st_mode))
- return kFileTypeCharDev;
- else if (S_ISBLK(sb.st_mode))
- return kFileTypeBlockDev;
- else if (S_ISFIFO(sb.st_mode))
- return kFileTypeFifo;
+FileType getFileType(const char* fileName)
+{
+ struct stat sb;
+
+ if (stat(fileName, &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return kFileTypeNonexistent;
+ else {
+ PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
+ return kFileTypeUnknown;
+ }
+ } else {
+ if (S_ISREG(sb.st_mode))
+ return kFileTypeRegular;
+ else if (S_ISDIR(sb.st_mode))
+ return kFileTypeDirectory;
+ else if (S_ISCHR(sb.st_mode))
+ return kFileTypeCharDev;
+ else if (S_ISBLK(sb.st_mode))
+ return kFileTypeBlockDev;
+ else if (S_ISFIFO(sb.st_mode))
+ return kFileTypeFifo;
#if defined(S_ISLNK)
else if (S_ISLNK(sb.st_mode))
return kFileTypeSymlink;
@@ -71,7 +75,7 @@ FileType getFileType(const char* fileName) {
}
}
-ModDate getModDate(const struct stat& st) {
+static ModDate getModDate(const struct stat& st) {
#ifdef _WIN32
return st.st_mtime;
#elif defined(__APPLE__)
@@ -109,14 +113,8 @@ bool isReadonlyFilesystem(const char*) {
bool isReadonlyFilesystem(int) {
return false;
}
-bool isKnownWritablePath(const char*) {
- return false;
-}
#else // __linux__
bool isReadonlyFilesystem(const char* path) {
- if (isKnownWritablePath(path)) {
- return false;
- }
struct statfs sfs;
if (::statfs(path, &sfs)) {
PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
@@ -133,13 +131,6 @@ bool isReadonlyFilesystem(int fd) {
}
return (sfs.f_flags & ST_RDONLY) != 0;
}
-
-bool isKnownWritablePath(const char* path) {
- // We know that all paths in /data/ are writable.
- static constexpr char kRwPrefix[] = "/data/";
- return strncmp(kRwPrefix, path, std::size(kRwPrefix) - 1) == 0;
-}
-
#endif // __linux__
} // namespace android
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 22b9e69500d9..cb2e56f5f5e4 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -218,11 +218,10 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
auto apk_assets = ApkAssets::LoadOverlay(temp_file.path);
ASSERT_NE(nullptr, apk_assets);
- ASSERT_TRUE(apk_assets->IsOverlay());
- ASSERT_EQ(UpToDate::True, apk_assets->IsUpToDate());
+ ASSERT_TRUE(apk_assets->IsUpToDate());
unlink(temp_file.path);
- ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
+ ASSERT_FALSE(apk_assets->IsUpToDate());
const auto sleep_duration =
std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull));
@@ -231,27 +230,7 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
base::WriteStringToFile("hello", temp_file.path);
std::this_thread::sleep_for(sleep_duration);
- ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
-}
-
-TEST(IdmapTestUpToDate, Combine) {
- ASSERT_EQ(UpToDate::False, combine(UpToDate::False, [] {
- ADD_FAILURE(); // Shouldn't get called at all.
- return UpToDate::False;
- }));
-
- ASSERT_EQ(UpToDate::False, combine(UpToDate::True, [] { return UpToDate::False; }));
-
- ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::True; }));
- ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::Always; }));
- ASSERT_EQ(UpToDate::True, combine(UpToDate::Always, [] { return UpToDate::True; }));
-
- ASSERT_EQ(UpToDate::Always, combine(UpToDate::Always, [] { return UpToDate::Always; }));
-}
-
-TEST(IdmapTestUpToDate, FromBool) {
- ASSERT_EQ(UpToDate::False, fromBool(false));
- ASSERT_EQ(UpToDate::True, fromBool(true));
+ ASSERT_FALSE(apk_assets->IsUpToDate());
}
} // namespace
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index f8fb6b7207a0..139ccfd22b0e 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -36,8 +36,7 @@ package com.android.extensions.appfunctions {
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 void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.content.pm.SigningInfo, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
- method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
index a09451ede4fc..81d9d81c4f58 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
@@ -25,7 +25,6 @@ import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.app.Service;
import android.content.Intent;
-import android.content.pm.SigningInfo;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -82,7 +81,6 @@ public abstract class AppFunctionService extends Service {
SidecarConverter.getSidecarExecuteAppFunctionRequest(
platformRequest),
callingPackage,
- callingPackageSigningInfo,
cancellationSignal,
new OutcomeReceiver<>() {
@Override
@@ -129,52 +127,10 @@ 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.
*/
@MainThread
- public void onExecuteFunction(
- @NonNull ExecuteAppFunctionRequest request,
- @NonNull String callingPackage,
- @NonNull SigningInfo callingPackageSigningInfo,
- @NonNull CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback) {
- onExecuteFunction(request, callingPackage, cancellationSignal, callback);
- }
-
- /**
- * Called by the system to execute a specific app function.
- *
- * <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>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
- * result using the callback, no matter if the execution was successful or not.
- *
- * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel
- * the execution of function if requested by the system.
- *
- * @param request The function execution request.
- * @param callingPackage The package name 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.
- * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, SigningInfo,
- * CancellationSignal, OutcomeReceiver)} instead.
- */
- @MainThread
- @Deprecated
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 7b45070af312..290df997a8ed 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -36,7 +36,7 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
const SkFont& font = paint->getSkFont();
- minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection);
+ minikin::MinikinPaint minikinPaint(resolvedFace->getFontCollection());
/* Prepare minikin Paint */
minikinPaint.size =
font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize());
@@ -46,9 +46,9 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
minikinPaint.wordSpacing = paint->getWordSpacing();
minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
minikinPaint.localeListId = paint->getMinikinLocaleListId();
- minikinPaint.fontStyle = resolvedFace->fStyle;
+ minikinPaint.fontStyle = resolvedFace->getFontStyle();
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
- if (!resolvedFace->fIsVariationInstance) {
+ if (!resolvedFace->isVariationInstance()) {
// This is an optimization for direct private API use typically done by System UI.
// In the public API surface, if Typeface is already configured for variation instance
// (Target SDK <= 35) the font variation settings of Paint is not set.
@@ -132,7 +132,7 @@ minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::
bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) {
const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
- return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
+ return resolvedFace->getFontCollection()->hasVariationSelector(codepoint, vs);
}
float MinikinUtils::xOffsetForTextAlign(Paint* paint, const minikin::Layout& layout) {
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 4dfe05377a48..a73aac632752 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -70,74 +70,45 @@ const Typeface* Typeface::resolveDefault(const Typeface* src) {
Typeface* Typeface::createRelative(Typeface* src, Typeface::Style style) {
const Typeface* resolvedFace = Typeface::resolveDefault(src);
- Typeface* result = new Typeface;
- if (result != nullptr) {
- result->fFontCollection = resolvedFace->fFontCollection;
- result->fBaseWeight = resolvedFace->fBaseWeight;
- result->fAPIStyle = style;
- result->fStyle = computeRelativeStyle(result->fBaseWeight, style);
- result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
- }
- return result;
+ return new Typeface(resolvedFace->getFontCollection(),
+ computeRelativeStyle(resolvedFace->getBaseWeight(), style), style,
+ resolvedFace->getBaseWeight(), resolvedFace->isVariationInstance());
}
Typeface* Typeface::createAbsolute(Typeface* base, int weight, bool italic) {
const Typeface* resolvedFace = Typeface::resolveDefault(base);
- Typeface* result = new Typeface();
- if (result != nullptr) {
- result->fFontCollection = resolvedFace->fFontCollection;
- result->fBaseWeight = resolvedFace->fBaseWeight;
- result->fAPIStyle = computeAPIStyle(weight, italic);
- result->fStyle = computeMinikinStyle(weight, italic);
- result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
- }
- return result;
+ return new Typeface(resolvedFace->getFontCollection(), computeMinikinStyle(weight, italic),
+ computeAPIStyle(weight, italic), resolvedFace->getBaseWeight(),
+ resolvedFace->isVariationInstance());
}
Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src,
const minikin::VariationSettings& variations) {
const Typeface* resolvedFace = Typeface::resolveDefault(src);
- Typeface* result = new Typeface();
- if (result != nullptr) {
- result->fFontCollection =
- resolvedFace->fFontCollection->createCollectionWithVariation(variations);
- if (result->fFontCollection == nullptr) {
+ const std::shared_ptr<minikin::FontCollection>& fc =
+ resolvedFace->getFontCollection()->createCollectionWithVariation(variations);
+ return new Typeface(
// None of passed axes are supported by this collection.
// So we will reuse the same collection with incrementing reference count.
- result->fFontCollection = resolvedFace->fFontCollection;
- }
- // Do not update styles.
- // TODO: We may want to update base weight if the 'wght' is specified.
- result->fBaseWeight = resolvedFace->fBaseWeight;
- result->fAPIStyle = resolvedFace->fAPIStyle;
- result->fStyle = resolvedFace->fStyle;
- result->fIsVariationInstance = true;
- }
- return result;
+ fc ? fc : resolvedFace->getFontCollection(),
+ // Do not update styles.
+ // TODO: We may want to update base weight if the 'wght' is specified.
+ resolvedFace->fStyle, resolvedFace->getAPIStyle(), resolvedFace->getBaseWeight(), true);
}
Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) {
const Typeface* resolvedFace = Typeface::resolveDefault(src);
- Typeface* result = new Typeface;
- if (result != nullptr) {
- result->fFontCollection = resolvedFace->fFontCollection;
- result->fBaseWeight = weight;
- result->fAPIStyle = resolvedFace->fAPIStyle;
- result->fStyle = computeRelativeStyle(weight, result->fAPIStyle);
- result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
- }
- return result;
+ return new Typeface(resolvedFace->getFontCollection(),
+ computeRelativeStyle(weight, resolvedFace->getAPIStyle()),
+ resolvedFace->getAPIStyle(), weight, resolvedFace->isVariationInstance());
}
Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
int weight, int italic, const Typeface* fallback) {
- Typeface* result = new Typeface;
- if (fallback == nullptr) {
- result->fFontCollection = minikin::FontCollection::create(std::move(families));
- } else {
- result->fFontCollection =
- fallback->fFontCollection->createCollectionWithFamilies(std::move(families));
- }
+ const std::shared_ptr<minikin::FontCollection>& fc =
+ fallback ? fallback->getFontCollection()->createCollectionWithFamilies(
+ std::move(families))
+ : minikin::FontCollection::create(std::move(families));
if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
int weightFromFont;
@@ -171,11 +142,8 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font
weight = SkFontStyle::kNormal_Weight;
}
- result->fBaseWeight = weight;
- result->fAPIStyle = computeAPIStyle(weight, italic);
- result->fStyle = computeMinikinStyle(weight, italic);
- result->fIsVariationInstance = false;
- return result;
+ return new Typeface(fc, computeMinikinStyle(weight, italic), computeAPIStyle(weight, italic),
+ weight, false);
}
void Typeface::setDefault(const Typeface* face) {
@@ -205,11 +173,8 @@ void Typeface::setRobotoTypefaceForTest() {
std::shared_ptr<minikin::FontCollection> collection =
minikin::FontCollection::create(minikin::FontFamily::create(std::move(fonts)));
- Typeface* hwTypeface = new Typeface();
- hwTypeface->fFontCollection = collection;
- hwTypeface->fAPIStyle = Typeface::kNormal;
- hwTypeface->fBaseWeight = SkFontStyle::kNormal_Weight;
- hwTypeface->fStyle = minikin::FontStyle();
+ Typeface* hwTypeface = new Typeface(collection, minikin::FontStyle(), Typeface::kNormal,
+ SkFontStyle::kNormal_Weight, false);
Typeface::setDefault(hwTypeface);
#endif
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 97d1bf4ef011..e8233a6bc6d8 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -32,21 +32,39 @@ constexpr int RESOLVE_BY_FONT_TABLE = -1;
struct ANDROID_API Typeface {
public:
- std::shared_ptr<minikin::FontCollection> fFontCollection;
+ enum Style : uint8_t { kNormal = 0, kBold = 0x01, kItalic = 0x02, kBoldItalic = 0x03 };
+ Typeface(const std::shared_ptr<minikin::FontCollection> fc, minikin::FontStyle style,
+ Style apiStyle, int baseWeight, bool isVariationInstance)
+ : fFontCollection(fc)
+ , fStyle(style)
+ , fAPIStyle(apiStyle)
+ , fBaseWeight(baseWeight)
+ , fIsVariationInstance(isVariationInstance) {}
+
+ const std::shared_ptr<minikin::FontCollection>& getFontCollection() const {
+ return fFontCollection;
+ }
// resolved style actually used for rendering
- minikin::FontStyle fStyle;
+ minikin::FontStyle getFontStyle() const { return fStyle; }
// style used in the API
- enum Style : uint8_t { kNormal = 0, kBold = 0x01, kItalic = 0x02, kBoldItalic = 0x03 };
- Style fAPIStyle;
+ Style getAPIStyle() const { return fAPIStyle; }
// base weight in CSS-style units, 1..1000
- int fBaseWeight;
+ int getBaseWeight() const { return fBaseWeight; }
// True if the Typeface is already created for variation settings.
- bool fIsVariationInstance;
+ bool isVariationInstance() const { return fIsVariationInstance; }
+private:
+ std::shared_ptr<minikin::FontCollection> fFontCollection;
+ minikin::FontStyle fStyle;
+ Style fAPIStyle;
+ int fBaseWeight;
+ bool fIsVariationInstance = false;
+
+public:
static const Typeface* resolveDefault(const Typeface* src);
// The following three functions create new Typeface from an existing Typeface with a different
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 8d3a5eb2b4af..f6fdec1c82bc 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -609,7 +609,8 @@ namespace PaintGlue {
SkFont* font = &paint->getSkFont();
const Typeface* typeface = paint->getAndroidTypeface();
typeface = Typeface::resolveDefault(typeface);
- minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
+ minikin::FakedFont baseFont =
+ typeface->getFontCollection()->baseFontFaked(typeface->getFontStyle());
float saveSkewX = font->getSkewX();
bool savefakeBold = font->isEmbolden();
MinikinFontSkia::populateSkFont(font, baseFont.typeface().get(), baseFont.fakery);
@@ -641,7 +642,7 @@ namespace PaintGlue {
if (useLocale) {
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
minikin::MinikinExtent extent =
- typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint);
+ typeface->getFontCollection()->getReferenceExtentForLocale(minikinPaint);
metrics->fAscent = std::min(extent.ascent, metrics->fAscent);
metrics->fDescent = std::max(extent.descent, metrics->fDescent);
metrics->fTop = std::min(metrics->fAscent, metrics->fTop);
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index c5095c1a0704..63906de80745 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -99,17 +99,17 @@ static jlong Typeface_getReleaseFunc(CRITICAL_JNI_PARAMS) {
// CriticalNative
static jint Typeface_getStyle(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- return toTypeface(faceHandle)->fAPIStyle;
+ return toTypeface(faceHandle)->getAPIStyle();
}
// CriticalNative
static jint Typeface_getWeight(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- return toTypeface(faceHandle)->fStyle.weight();
+ return toTypeface(faceHandle)->getFontStyle().weight();
}
// Critical Native
static jboolean Typeface_isVariationInstance(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- return toTypeface(faceHandle)->fIsVariationInstance;
+ return toTypeface(faceHandle)->isVariationInstance();
}
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
@@ -128,18 +128,18 @@ static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArr
// CriticalNative
static void Typeface_setDefault(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
Typeface::setDefault(toTypeface(faceHandle));
- minikin::SystemFonts::registerDefault(toTypeface(faceHandle)->fFontCollection);
+ minikin::SystemFonts::registerDefault(toTypeface(faceHandle)->getFontCollection());
}
static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) {
Typeface* face = toTypeface(faceHandle);
- const size_t length = face->fFontCollection->getSupportedAxesCount();
+ const size_t length = face->getFontCollection()->getSupportedAxesCount();
if (length == 0) {
return nullptr;
}
std::vector<jint> tagVec(length);
for (size_t i = 0; i < length; i++) {
- tagVec[i] = face->fFontCollection->getSupportedAxisAt(i);
+ tagVec[i] = face->getFontCollection()->getSupportedAxisAt(i);
}
std::sort(tagVec.begin(), tagVec.end());
const jintArray result = env->NewIntArray(length);
@@ -150,7 +150,7 @@ static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle)
static void Typeface_registerGenericFamily(JNIEnv *env, jobject, jstring familyName, jlong ptr) {
ScopedUtfChars familyNameChars(env, familyName);
minikin::SystemFonts::registerFallback(familyNameChars.c_str(),
- toTypeface(ptr)->fFontCollection);
+ toTypeface(ptr)->getFontCollection());
}
#ifdef __ANDROID__
@@ -315,18 +315,19 @@ static jint Typeface_writeTypefaces(JNIEnv* env, jobject, jobject buffer, jint p
std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections;
std::unordered_map<std::shared_ptr<minikin::FontCollection>, size_t> fcToIndex;
for (Typeface* typeface : typefaces) {
- bool inserted = fcToIndex.emplace(typeface->fFontCollection, fontCollections.size()).second;
+ bool inserted =
+ fcToIndex.emplace(typeface->getFontCollection(), fontCollections.size()).second;
if (inserted) {
- fontCollections.push_back(typeface->fFontCollection);
+ fontCollections.push_back(typeface->getFontCollection());
}
}
minikin::FontCollection::writeVector(&writer, fontCollections);
writer.write<uint32_t>(typefaces.size());
for (Typeface* typeface : typefaces) {
- writer.write<uint32_t>(fcToIndex.find(typeface->fFontCollection)->second);
- typeface->fStyle.writeTo(&writer);
- writer.write<Typeface::Style>(typeface->fAPIStyle);
- writer.write<int>(typeface->fBaseWeight);
+ writer.write<uint32_t>(fcToIndex.find(typeface->getFontCollection())->second);
+ typeface->getFontStyle().writeTo(&writer);
+ writer.write<Typeface::Style>(typeface->getAPIStyle());
+ writer.write<int>(typeface->getBaseWeight());
}
return static_cast<jint>(writer.size());
}
@@ -349,12 +350,10 @@ static jlongArray Typeface_readTypefaces(JNIEnv* env, jobject, jobject buffer, j
std::vector<jlong> faceHandles;
faceHandles.reserve(typefaceCount);
for (uint32_t i = 0; i < typefaceCount; i++) {
- Typeface* typeface = new Typeface;
- typeface->fFontCollection = fontCollections[reader.read<uint32_t>()];
- typeface->fStyle = minikin::FontStyle(&reader);
- typeface->fAPIStyle = reader.read<Typeface::Style>();
- typeface->fBaseWeight = reader.read<int>();
- typeface->fIsVariationInstance = false;
+ Typeface* typeface =
+ new Typeface(fontCollections[reader.read<uint32_t>()], minikin::FontStyle(&reader),
+ reader.read<Typeface::Style>(), reader.read<int>(),
+ false /* isVariationInstance */);
faceHandles.push_back(toJLong(typeface));
}
const jlongArray result = env->NewLongArray(typefaceCount);
@@ -382,7 +381,8 @@ static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) {
// Critical Native
static void Typeface_addFontCollection(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- std::shared_ptr<minikin::FontCollection> collection = toTypeface(faceHandle)->fFontCollection;
+ std::shared_ptr<minikin::FontCollection> collection =
+ toTypeface(faceHandle)->getFontCollection();
minikin::SystemFonts::addFontMap(std::move(collection));
}
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index d1782b285b34..7a4ae8330de8 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -104,7 +104,7 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou
} else {
fontId = fonts.size(); // This is new to us. Create new one.
std::shared_ptr<minikin::Font> font;
- if (resolvedFace->fIsVariationInstance) {
+ if (resolvedFace->isVariationInstance()) {
// The optimization for target SDK 35 or before because the variation instance
// is already created and no runtime variation resolution happens on such
// environment.
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 6571d92aeafa..a67aea466c1c 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -729,7 +729,7 @@ VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) {
VkSemaphore semaphore;
VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
ALOGE_IF(VK_SUCCESS != err,
- "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
+ "VulkanManager::finishFrame(): Failed to create semaphore");
if (err == VK_SUCCESS) {
sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
@@ -777,7 +777,7 @@ VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) {
int fenceFd = -1;
VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
- ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
+ ALOGE_IF(VK_SUCCESS != err, "VulkanManager::finishFrame(): Failed to get semaphore Fd");
drawResult.presentFence.reset(fenceFd);
} else {
ALOGE("VulkanManager::finishFrame(): Semaphore submission failed");
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 93118aeafaaf..b51414fd3c02 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -183,8 +183,11 @@ SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) {
}
SkFont TestUtils::defaultFont() {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont =
- Typeface::resolveDefault(nullptr)->fFontCollection->getFamilyAt(0)->getFont(0)->baseTypeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = Typeface::resolveDefault(nullptr)
+ ->getFontCollection()
+ ->getFamilyAt(0)
+ ->getFont(0)
+ ->baseTypeface();
SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(minikinFont.get())->GetSkTypeface();
LOG_ALWAYS_FATAL_IF(skTypeface == nullptr);
return SkFont(sk_ref_sp(skTypeface));
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index c71c4d243a8b..7bcd937397b0 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -90,40 +90,40 @@ TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
TEST(TypefaceTest, createWithDifferentBaseWeight) {
std::unique_ptr<Typeface> bold(Typeface::createWithDifferentBaseWeight(nullptr, 700));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, bold->getAPIStyle());
std::unique_ptr<Typeface> light(Typeface::createWithDifferentBaseWeight(nullptr, 300));
- EXPECT_EQ(300, light->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, light->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, light->fAPIStyle);
+ EXPECT_EQ(300, light->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, light->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, light->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_fromRegular) {
// In Java, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(nullptr, Typeface::kNormal));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(nullptr, Typeface::kBold));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, Typeface.create(Typeface.DEFAULT, Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(nullptr, Typeface::kItalic));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(Typeface::createRelative(nullptr, Typeface::kBoldItalic));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_BoldBase) {
@@ -132,31 +132,31 @@ TEST(TypefaceTest, createRelativeTest_BoldBase) {
// In Java, Typeface.create(Typeface.create("sans-serif-bold"),
// Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(700, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(700, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-bold"),
// Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(1000, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(1000, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-bold"),
// Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(700, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(700, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-bold"),
// Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(1000, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(1000, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_LightBase) {
@@ -165,31 +165,31 @@ TEST(TypefaceTest, createRelativeTest_LightBase) {
// In Java, Typeface.create(Typeface.create("sans-serif-light"),
// Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(300, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(300, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-light"),
// Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(600, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(600, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-light"),
// Typeface.ITLIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(300, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(300, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-light"),
// Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(600, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(600, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_fromBoldStyled) {
@@ -198,32 +198,32 @@ TEST(TypefaceTest, createRelativeTest_fromBoldStyled) {
// In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
// Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
// Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
// Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
// Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_fromItalicStyled) {
@@ -233,33 +233,33 @@ TEST(TypefaceTest, createRelativeTest_fromItalicStyled) {
// Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
// Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java, Typeface.create(Typeface.create(Typeface.DEFAULT,
// Typeface.ITALIC), Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java,
// Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
// Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
// Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_fromSpecifiedStyled) {
@@ -270,27 +270,27 @@ TEST(TypefaceTest, createRelativeTest_fromSpecifiedStyled) {
// .setWeight(700).setItalic(false).build();
// Typeface.create(typeface, Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java,
// Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
// .setWeight(700).setItalic(false).build();
// Typeface.create(typeface, Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java,
// Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
// .setWeight(700).setItalic(false).build();
// Typeface.create(typeface, Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
@@ -298,9 +298,9 @@ TEST(TypefaceTest, createRelativeTest_fromSpecifiedStyled) {
// Typeface.create(typeface, Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createAbsolute) {
@@ -309,45 +309,45 @@ TEST(TypefaceTest, createAbsolute) {
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(false)
// .build();
std::unique_ptr<Typeface> regular(Typeface::createAbsolute(nullptr, 400, false));
- EXPECT_EQ(400, regular->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
+ EXPECT_EQ(400, regular->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, regular->getAPIStyle());
// In Java,
// new
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(false)
// .build();
std::unique_ptr<Typeface> bold(Typeface::createAbsolute(nullptr, 700, false));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java,
// new
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(true)
// .build();
std::unique_ptr<Typeface> italic(Typeface::createAbsolute(nullptr, 400, true));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// new
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(true)
// .build();
std::unique_ptr<Typeface> boldItalic(Typeface::createAbsolute(nullptr, 700, true));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
// In Java,
// new
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(1100).setItalic(true)
// .build();
std::unique_ptr<Typeface> over1000(Typeface::createAbsolute(nullptr, 1100, false));
- EXPECT_EQ(1000, over1000->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
+ EXPECT_EQ(1000, over1000->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, over1000->getAPIStyle());
}
TEST(TypefaceTest, createFromFamilies_Single) {
@@ -355,43 +355,43 @@ TEST(TypefaceTest, createFromFamilies_Single) {
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 400, false, nullptr /* fallback */));
- EXPECT_EQ(400, regular->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
+ EXPECT_EQ(400, regular->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, regular->getAPIStyle());
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build();
std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 700, false, nullptr /* fallback */));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build();
std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 400, true, nullptr /* fallback */));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build();
std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 700, true, nullptr /* fallback */));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build();
std::unique_ptr<Typeface> over1000(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 1100, false, nullptr /* fallback */));
- EXPECT_EQ(1000, over1000->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
+ EXPECT_EQ(1000, over1000->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, over1000->getAPIStyle());
}
TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
@@ -399,33 +399,33 @@ TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
std::unique_ptr<Typeface> regular(
Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(400, regular->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
+ EXPECT_EQ(400, regular->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, regular->getAPIStyle());
// In Java, new Typeface.Builder("Family-Bold.ttf").build();
std::unique_ptr<Typeface> bold(
Typeface::createFromFamilies(makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, new Typeface.Builder("Family-Italic.ttf").build();
std::unique_ptr<Typeface> italic(
Typeface::createFromFamilies(makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java, new Typeface.Builder("Family-BoldItalic.ttf").build();
std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
makeSingleFamlyVector(kBoldItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
nullptr /* fallback */));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
}
TEST(TypefaceTest, createFromFamilies_Family) {
@@ -435,8 +435,8 @@ TEST(TypefaceTest, createFromFamilies_Family) {
std::unique_ptr<Typeface> typeface(
Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(400, typeface->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
+ EXPECT_EQ(400, typeface->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->getFontStyle().slant());
}
TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
@@ -445,8 +445,8 @@ TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
std::unique_ptr<Typeface> typeface(
Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(700, typeface->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
+ EXPECT_EQ(700, typeface->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->getFontStyle().slant());
}
TEST(TypefaceTest, createFromFamilies_Family_withFallback) {
@@ -458,8 +458,8 @@ TEST(TypefaceTest, createFromFamilies_Family_withFallback) {
std::unique_ptr<Typeface> regular(
Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, fallback.get()));
- EXPECT_EQ(400, regular->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
+ EXPECT_EQ(400, regular->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->getFontStyle().slant());
}
} // namespace
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/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 54a87ad7fd39..2a740f85aa72 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -32,6 +32,7 @@ import android.media.BluetoothProfileConnectionInfo;
import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
+import android.media.IAudioManagerNative;
import android.media.IAudioModeDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IAudioServerStateDispatcher;
@@ -83,6 +84,7 @@ interface IAudioService {
// When a method's argument list is changed, BpAudioManager's corresponding serialization code
// (if any) in frameworks/native/services/audiomanager/IAudioManager.cpp must be updated.
+ IAudioManagerNative getNativeInterface();
int trackPlayer(in PlayerBase.PlayerIdCard pic);
oneway void playerAttributes(in int piid, in AudioAttributes attr);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index bbb03e77c8c9..88981eac9bb5 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -961,8 +961,7 @@ public final class MediaRoute2Info implements Parcelable {
*
* @hide
*/
- @FlaggedApi(FLAG_ENABLE_MEDIA_ROUTE_2_INFO_PROVIDER_PACKAGE_NAME)
- public boolean isVisibleTo(String packageName) {
+ public boolean isVisibleTo(@NonNull String packageName) {
return !mIsVisibilityRestricted
|| TextUtils.equals(getProviderPackageName(), packageName)
|| mAllowedPackages.contains(packageName);
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3738312b762f..e57148fe5a6a 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -19,7 +19,6 @@ package android.media;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
import static com.android.media.flags.Flags.FLAG_ENABLE_GET_TRANSFERABLE_ROUTES;
-import static com.android.media.flags.Flags.FLAG_ENABLE_MEDIA_ROUTE_2_INFO_PROVIDER_PACKAGE_NAME;
import static com.android.media.flags.Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL;
import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
import static com.android.media.flags.Flags.FLAG_ENABLE_SCREEN_OFF_SCANNING;
@@ -1406,7 +1405,6 @@ public final class MediaRouter2 {
requestCreateController(controller, route, managerRequestId);
}
- @FlaggedApi(FLAG_ENABLE_MEDIA_ROUTE_2_INFO_PROVIDER_PACKAGE_NAME)
private List<MediaRoute2Info> getSortedRoutes(
List<MediaRoute2Info> routes, List<String> packageOrder) {
if (packageOrder.isEmpty()) {
@@ -1427,7 +1425,6 @@ public final class MediaRouter2 {
}
@GuardedBy("mLock")
- @FlaggedApi(FLAG_ENABLE_MEDIA_ROUTE_2_INFO_PROVIDER_PACKAGE_NAME)
private List<MediaRoute2Info> filterRoutesWithCompositePreferenceLocked(
List<MediaRoute2Info> routes) {
@@ -3654,7 +3651,6 @@ public final class MediaRouter2 {
}
}
- @FlaggedApi(FLAG_ENABLE_MEDIA_ROUTE_2_INFO_PROVIDER_PACKAGE_NAME)
@Override
public List<MediaRoute2Info> filterRoutesWithIndividualPreference(
List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference) {
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 3854747f46e0..3f18eef2f9aa 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -20,11 +20,9 @@ import static android.media.MediaRouter2.SCANNING_STATE_NOT_SCANNING;
import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.media.flags.Flags.FLAG_ENABLE_MEDIA_ROUTE_2_INFO_PROVIDER_PACKAGE_NAME;
import android.Manifest;
import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -287,7 +285,6 @@ public final class MediaRouter2Manager {
(route) -> sessionInfo.isSystemSession() ^ route.isSystemRoute());
}
- @FlaggedApi(FLAG_ENABLE_MEDIA_ROUTE_2_INFO_PROVIDER_PACKAGE_NAME)
private List<MediaRoute2Info> getSortedRoutes(RouteDiscoveryPreference preference) {
if (!preference.shouldRemoveDuplicates()) {
synchronized (mRoutesLock) {
@@ -311,7 +308,6 @@ public final class MediaRouter2Manager {
return routes;
}
- @FlaggedApi(FLAG_ENABLE_MEDIA_ROUTE_2_INFO_PROVIDER_PACKAGE_NAME)
private List<MediaRoute2Info> getFilteredRoutes(
@NonNull RoutingSessionInfo sessionInfo,
boolean includeSelectedRoutes,
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 4398b261377b..c48b5f4e4aea 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -11,6 +11,16 @@ flag {
}
flag {
+ name: "disable_set_bluetooth_ad2p_on_calls"
+ namespace: "media_better_together"
+ description: "Prevents calls to AudioService.setBluetoothA2dpOn(), known to cause incorrect audio routing to the built-in speakers."
+ bug: "294968421"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_audio_input_device_routing_and_volume_control"
namespace: "media_better_together"
description: "Allows audio input devices routing and volume control via system settings."
diff --git a/mime/Android.bp b/mime/Android.bp
index 20110f1dfb47..b609548fcbab 100644
--- a/mime/Android.bp
+++ b/mime/Android.bp
@@ -49,6 +49,17 @@ java_library {
],
}
+java_library {
+ name: "mimemap-testing-alt",
+ defaults: ["mimemap-defaults"],
+ static_libs: ["mimemap-testing-alt-res.jar"],
+ jarjar_rules: "jarjar-rules-alt.txt",
+ visibility: [
+ "//cts/tests/tests/mimemap:__subpackages__",
+ "//frameworks/base:__subpackages__",
+ ],
+}
+
// The mimemap-res.jar and mimemap-testing-res.jar genrules produce a .jar that
// has the resource file in a subdirectory res/ and testres/, respectively.
// They need to be in different paths because one of them ends up in a
@@ -86,6 +97,19 @@ java_genrule {
cmd: "mkdir $(genDir)/testres/ && cp $(in) $(genDir)/testres/ && $(location soong_zip) -C $(genDir) -o $(out) -D $(genDir)/testres/",
}
+// The same as mimemap-testing-res.jar except that the resources are placed in a different directory.
+// They get bundled with CTS so that CTS can compare a device's MimeMap implementation vs.
+// the stock Android one from when CTS was built.
+java_genrule {
+ name: "mimemap-testing-alt-res.jar",
+ tools: [
+ "soong_zip",
+ ],
+ srcs: [":mime.types.minimized-alt"],
+ out: ["mimemap-testing-alt-res.jar"],
+ cmd: "mkdir $(genDir)/testres-alt/ && cp $(in) $(genDir)/testres-alt/ && $(location soong_zip) -C $(genDir) -o $(out) -D $(genDir)/testres-alt/",
+}
+
// Combination of all *mime.types.minimized resources.
filegroup {
name: "mime.types.minimized",
@@ -99,6 +123,19 @@ filegroup {
],
}
+// Combination of all *mime.types.minimized resources.
+filegroup {
+ name: "mime.types.minimized-alt",
+ visibility: [
+ "//visibility:private",
+ ],
+ device_common_srcs: [
+ ":debian.mime.types.minimized-alt",
+ ":android.mime.types.minimized",
+ ":vendor.mime.types.minimized",
+ ],
+}
+
java_genrule {
name: "android.mime.types.minimized",
visibility: [
diff --git a/mime/jarjar-rules-alt.txt b/mime/jarjar-rules-alt.txt
new file mode 100644
index 000000000000..9a7644325336
--- /dev/null
+++ b/mime/jarjar-rules-alt.txt
@@ -0,0 +1 @@
+rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidAltMimeMapFactory
diff --git a/mime/jarjar-rules.txt b/mime/jarjar-rules.txt
index 145d1dbf3d11..e1ea8e10314c 100644
--- a/mime/jarjar-rules.txt
+++ b/mime/jarjar-rules.txt
@@ -1 +1 @@
-rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidMimeMapFactory \ No newline at end of file
+rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidMimeMapFactory
diff --git a/native/android/tests/system_health/OWNERS b/native/android/tests/system_health/OWNERS
new file mode 100644
index 000000000000..e3bbee92057d
--- /dev/null
+++ b/native/android/tests/system_health/OWNERS
@@ -0,0 +1 @@
+include /ADPF_OWNERS
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
index 068074ae1b89..8e52a00fe545 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
@@ -38,6 +38,7 @@ import android.location.provider.LocationProviderBase;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.os.Bundle;
+import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -301,8 +302,13 @@ public class FusedLocationProvider extends LocationProviderBase {
.setWorkSource(mRequest.getWorkSource())
.setHiddenFromAppOps(true)
.build();
- mLocationManager.requestLocationUpdates(mProvider, request,
- mContext.getMainExecutor(), this);
+
+ try {
+ mLocationManager.requestLocationUpdates(
+ mProvider, request, mContext.getMainExecutor(), this);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to request location updates");
+ }
}
}
}
@@ -311,7 +317,11 @@ public class FusedLocationProvider extends LocationProviderBase {
synchronized (mLock) {
int requestCode = mNextFlushCode++;
mPendingFlushes.put(requestCode, callback);
- mLocationManager.requestFlush(mProvider, this, requestCode);
+ try {
+ mLocationManager.requestFlush(mProvider, this, requestCode);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to request flush");
+ }
}
}
diff --git a/packages/PrintSpooler/Android.bp b/packages/PrintSpooler/Android.bp
index 6af3c6624f62..000e20fb4280 100644
--- a/packages/PrintSpooler/Android.bp
+++ b/packages/PrintSpooler/Android.bp
@@ -59,6 +59,21 @@ android_library {
"android-support-core-ui",
"android-support-fragment",
"android-support-annotations",
+ "printspooler_aconfig_flags_java_lib",
],
manifest: "AndroidManifest.xml",
}
+
+aconfig_declarations {
+ name: "printspooler_aconfig_declarations",
+ package: "com.android.printspooler.flags",
+ container: "system",
+ srcs: [
+ "flags/flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "printspooler_aconfig_flags_java_lib",
+ aconfig_declarations: "printspooler_aconfig_declarations",
+}
diff --git a/packages/PrintSpooler/flags/flags.aconfig b/packages/PrintSpooler/flags/flags.aconfig
new file mode 100644
index 000000000000..4a76dff405d0
--- /dev/null
+++ b/packages/PrintSpooler/flags/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.printspooler.flags"
+container: "system"
+
+flag {
+ name: "log_print_jobs"
+ namespace: "printing"
+ description: "Log print job creation and state transitions."
+ bug: "385340868"
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index bba57d5fe0a2..1a9309c13bd7 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -68,6 +68,7 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.printspooler.R;
+import com.android.printspooler.flags.Flags;
import com.android.printspooler.util.ApprovedPrintServices;
import libcore.io.IoUtils;
@@ -493,7 +494,7 @@ public final class PrintSpoolerService extends Service {
keepAwakeLocked();
}
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ if (Flags.logPrintJobs() || DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[ADD] " + printJob);
}
}
@@ -506,7 +507,7 @@ public final class PrintSpoolerService extends Service {
PrintJobInfo printJob = mPrintJobs.get(i);
if (isObsoleteState(printJob.getState())) {
mPrintJobs.remove(i);
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ if (Flags.logPrintJobs() || DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
}
removePrintJobFileLocked(printJob.getId());
@@ -568,7 +569,7 @@ public final class PrintSpoolerService extends Service {
checkIfStillKeepAwakeLocked();
}
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ if (Flags.logPrintJobs() || DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
index 2fac54557bef..6fc6b5405eb2 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
@@ -22,6 +22,7 @@ import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.ipc.ApiDescriptor
import com.android.settingslib.ipc.ApiHandler
import com.android.settingslib.ipc.ApiPermissionChecker
+import com.android.settingslib.metadata.PreferenceCoordinate
import com.android.settingslib.metadata.PreferenceHierarchyNode
import com.android.settingslib.metadata.PreferenceScreenRegistry
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt
index ff14eb5aae55..70ce62c8383c 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt
@@ -20,6 +20,7 @@ import android.os.Bundle
import android.os.Parcel
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.ipc.MessageCodec
+import com.android.settingslib.metadata.PreferenceCoordinate
import java.util.Arrays
/** Message codec for [PreferenceGetterRequest]. */
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt
index 68aa2d258295..2dd736ae6083 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.graph
+package com.android.settingslib.metadata
import android.os.Parcel
import android.os.Parcelable
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index e1e1ee5a8feb..78d6c31ac783 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -88,6 +88,7 @@ class AppListRepositoryImpl(
matchAnyUserForAdmin: Boolean,
): List<ApplicationInfo> = try {
coroutineScope {
+ // TODO(b/382016780): to be removed after flag cleanup.
val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
val hideWhenDisabledPackagesDeferred = async {
context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
@@ -95,6 +96,7 @@ class AppListRepositoryImpl(
val installedApplicationsAsUser =
getInstalledApplications(userId, matchAnyUserForAdmin)
+ // TODO(b/382016780): to be removed after flag cleanup.
val hiddenSystemModules = hiddenSystemModulesDeferred.await()
val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
installedApplicationsAsUser.filter { app ->
@@ -206,6 +208,7 @@ class AppListRepositoryImpl(
private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean =
app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages
+ // TODO(b/382016780): to be removed after flag cleanup.
private fun PackageManager.getHiddenSystemModules(): Set<String> {
val moduleInfos = getInstalledModules(0).filter { it.isHidden }
val hiddenApps = moduleInfos.mapNotNull { it.packageName }.toMutableSet()
@@ -218,13 +221,14 @@ class AppListRepositoryImpl(
companion object {
private const val TAG = "AppListRepository"
+ // TODO(b/382016780): to be removed after flag cleanup.
private fun ApplicationInfo.isInAppList(
showInstantApps: Boolean,
hiddenSystemModules: Set<String>,
hideWhenDisabledPackages: Array<String>,
) = when {
!showInstantApps && isInstantApp -> false
- packageName in hiddenSystemModules -> false
+ !Flags.removeHiddenModuleUsage() && (packageName in hiddenSystemModules) -> false
packageName in hideWhenDisabledPackages -> enabled && !isDisabledUntilUsed
enabled -> true
else -> enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index b1baa8601f28..fd4b189c51ff 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -281,6 +281,23 @@ class AppListRepositoryTest {
)
}
+ @EnableFlags(Flags.FLAG_REMOVE_HIDDEN_MODULE_USAGE)
+ @Test
+ fun loadApps_shouldIncludeAllSystemModuleApps() = runTest {
+ packageManager.stub {
+ on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+ }
+ mockInstalledApplications(
+ listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+ ADMIN_USER_ID
+ )
+
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP)
+ }
+
+ @DisableFlags(Flags.FLAG_REMOVE_HIDDEN_MODULE_USAGE)
@EnableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
@Test
fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest {
@@ -297,7 +314,7 @@ class AppListRepositoryTest {
assertThat(appList).containsExactly(NORMAL_APP)
}
- @DisableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+ @DisableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX, Flags.FLAG_REMOVE_HIDDEN_MODULE_USAGE)
@Test
fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest {
packageManager.stub {
@@ -456,6 +473,7 @@ class AppListRepositoryTest {
isArchived = true
}
+ // TODO(b/382016780): to be removed after flag cleanup.
val HIDDEN_APEX_APP = ApplicationInfo().apply {
packageName = "hidden.apex.package"
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index c4829951d61a..3390296ef6fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -137,6 +137,7 @@ public class AppUtils {
/**
* Returns a boolean indicating whether the given package is a hidden system module
+ * TODO(b/382016780): to be removed after flag cleanup.
*/
public static boolean isHiddenSystemModule(Context context, String packageName) {
return ApplicationsState.getInstance((Application) context.getApplicationContext())
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fd9a008ee078..4110d536da61 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -157,6 +157,7 @@ public class ApplicationsState {
int mCurComputingSizeUserId;
boolean mSessionsChanged;
// Maps all installed modules on the system to whether they're hidden or not.
+ // TODO(b/382016780): to be removed after flag cleanup.
final HashMap<String, Boolean> mSystemModules = new HashMap<>();
// Temporary for dispatching session callbacks. Only touched by main thread.
@@ -226,12 +227,14 @@ public class ApplicationsState {
mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS |
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
- final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
- for (ModuleInfo info : moduleInfos) {
- mSystemModules.put(info.getPackageName(), info.isHidden());
- if (Flags.provideInfoOfApkInApex()) {
- for (String apkInApexPackageName : info.getApkInApexPackageNames()) {
- mSystemModules.put(apkInApexPackageName, info.isHidden());
+ if (!Flags.removeHiddenModuleUsage()) {
+ final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
+ for (ModuleInfo info : moduleInfos) {
+ mSystemModules.put(info.getPackageName(), info.isHidden());
+ if (Flags.provideInfoOfApkInApex()) {
+ for (String apkInApexPackageName : info.getApkInApexPackageNames()) {
+ mSystemModules.put(apkInApexPackageName, info.isHidden());
+ }
}
}
}
@@ -336,7 +339,7 @@ public class ApplicationsState {
}
mHaveDisabledApps = true;
}
- if (isHiddenModule(info.packageName)) {
+ if (!Flags.removeHiddenModuleUsage() && isHiddenModule(info.packageName)) {
mApplications.remove(i--);
continue;
}
@@ -453,6 +456,7 @@ public class ApplicationsState {
return mHaveInstantApps;
}
+ // TODO(b/382016780): to be removed after flag cleanup.
boolean isHiddenModule(String packageName) {
Boolean isHidden = mSystemModules.get(packageName);
if (isHidden == null) {
@@ -462,6 +466,7 @@ public class ApplicationsState {
return isHidden;
}
+ // TODO(b/382016780): to be removed after flag cleanup.
boolean isSystemModule(String packageName) {
return mSystemModules.containsKey(packageName);
}
@@ -755,7 +760,7 @@ public class ApplicationsState {
Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
}
if (entry == null) {
- if (isHiddenModule(info.packageName)) {
+ if (!Flags.removeHiddenModuleUsage() && isHiddenModule(info.packageName)) {
if (DEBUG) {
Log.i(TAG, "No AppEntry for " + info.packageName + " (hidden module)");
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index b2c279466ee4..e05f0a1bcde0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -483,14 +483,18 @@ public class HearingAidDeviceManager {
void onActiveDeviceChanged(CachedBluetoothDevice device) {
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING)) {
- if (device.isConnectedHearingAidDevice()) {
+ if (device.isConnectedHearingAidDevice()
+ && (device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ || device.isActiveDevice(BluetoothProfile.LE_AUDIO))) {
setAudioRoutingConfig(device);
} else {
clearAudioRoutingConfig();
}
}
if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
- if (device.isConnectedHearingAidDevice()) {
+ if (device.isConnectedHearingAidDevice()
+ && (device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ || device.isActiveDevice(BluetoothProfile.LE_AUDIO))) {
setMicrophoneForCalls(device);
} else {
clearMicrophoneForCalls();
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index dc40304ba24a..51259e2f311d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -16,6 +16,8 @@
package com.android.settingslib.dream;
+import static android.service.dreams.Flags.allowDreamWhenPostured;
+
import android.annotation.IntDef;
import android.content.ComponentName;
import android.content.Context;
@@ -78,14 +80,21 @@ public class DreamBackend {
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER})
+ @IntDef({
+ WHILE_CHARGING,
+ WHILE_DOCKED,
+ WHILE_POSTURED,
+ WHILE_CHARGING_OR_DOCKED,
+ NEVER
+ })
public @interface WhenToDream {
}
public static final int WHILE_CHARGING = 0;
public static final int WHILE_DOCKED = 1;
- public static final int EITHER = 2;
- public static final int NEVER = 3;
+ public static final int WHILE_POSTURED = 2;
+ public static final int WHILE_CHARGING_OR_DOCKED = 3;
+ public static final int NEVER = 4;
/**
* The type of dream complications which can be provided by a
@@ -134,6 +143,8 @@ public class DreamBackend {
.DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_CHARGING_ONLY;
private static final int WHEN_TO_DREAM_DOCKED = FrameworkStatsLog
.DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_DOCKED_ONLY;
+ private static final int WHEN_TO_DREAM_POSTURED = FrameworkStatsLog
+ .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_POSTURED_ONLY;
private static final int WHEN_TO_DREAM_CHARGING_OR_DOCKED = FrameworkStatsLog
.DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_EITHER_CHARGING_OR_DOCKED;
@@ -143,6 +154,7 @@ public class DreamBackend {
private final boolean mDreamsEnabledByDefault;
private final boolean mDreamsActivatedOnSleepByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
+ private final boolean mDreamsActivatedOnPosturedByDefault;
private final Set<ComponentName> mDisabledDreams;
private final List<String> mLoggableDreamPrefixes;
private Set<Integer> mSupportedComplications;
@@ -168,6 +180,8 @@ public class DreamBackend {
com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
mDreamsActivatedOnDockByDefault = resources.getBoolean(
com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
+ mDreamsActivatedOnPosturedByDefault = resources.getBoolean(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault);
mDisabledDreams = Arrays.stream(resources.getStringArray(
com.android.internal.R.array.config_disabledDreamComponents))
.map(ComponentName::unflattenFromString)
@@ -280,10 +294,11 @@ public class DreamBackend {
@WhenToDream
public int getWhenToDreamSetting() {
- return isActivatedOnDock() && isActivatedOnSleep() ? EITHER
+ return isActivatedOnDock() && isActivatedOnSleep() ? WHILE_CHARGING_OR_DOCKED
: isActivatedOnDock() ? WHILE_DOCKED
- : isActivatedOnSleep() ? WHILE_CHARGING
- : NEVER;
+ : isActivatedOnPostured() ? WHILE_POSTURED
+ : isActivatedOnSleep() ? WHILE_CHARGING
+ : NEVER;
}
public void setWhenToDream(@WhenToDream int whenToDream) {
@@ -293,16 +308,25 @@ public class DreamBackend {
case WHILE_CHARGING:
setActivatedOnDock(false);
setActivatedOnSleep(true);
+ setActivatedOnPostured(false);
break;
case WHILE_DOCKED:
setActivatedOnDock(true);
setActivatedOnSleep(false);
+ setActivatedOnPostured(false);
break;
- case EITHER:
+ case WHILE_CHARGING_OR_DOCKED:
setActivatedOnDock(true);
setActivatedOnSleep(true);
+ setActivatedOnPostured(false);
+ break;
+
+ case WHILE_POSTURED:
+ setActivatedOnPostured(true);
+ setActivatedOnSleep(false);
+ setActivatedOnDock(false);
break;
case NEVER:
@@ -407,6 +431,22 @@ public class DreamBackend {
setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, value);
}
+ public boolean isActivatedOnPostured() {
+ return allowDreamWhenPostured()
+ && getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ mDreamsActivatedOnPosturedByDefault);
+ }
+
+ /**
+ * Sets whether dreams should be activated when the device is postured (stationary and upright)
+ */
+ public void setActivatedOnPostured(boolean value) {
+ if (allowDreamWhenPostured()) {
+ logd("setActivatedOnPostured(%s)", value);
+ setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, value);
+ }
+ }
+
private boolean getBoolean(String key, boolean def) {
return Settings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1;
}
@@ -548,7 +588,9 @@ public class DreamBackend {
return WHEN_TO_DREAM_CHARGING;
case WHILE_DOCKED:
return WHEN_TO_DREAM_DOCKED;
- case EITHER:
+ case WHILE_POSTURED:
+ return WHEN_TO_DREAM_POSTURED;
+ case WHILE_CHARGING_OR_DOCKED:
return WHEN_TO_DREAM_CHARGING_OR_DOCKED;
case NEVER:
default:
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/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 496c3e6c74cc..9aaefe47fda2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -37,7 +37,8 @@ class FakeZenModeRepository : ZenModeRepository {
override val globalZenMode: StateFlow<Int>
get() = mutableZenMode.asStateFlow()
- private val mutableModesFlow: MutableStateFlow<List<ZenMode>> = MutableStateFlow(listOf())
+ private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
+ MutableStateFlow(listOf(TestModeBuilder.MANUAL_DND))
override val modes: Flow<List<ZenMode>>
get() = mutableModesFlow.asStateFlow()
@@ -65,8 +66,11 @@ class FakeZenModeRepository : ZenModeRepository {
mutableModesFlow.value += mode
}
- fun addMode(id: String, @AutomaticZenRule.Type type: Int = AutomaticZenRule.TYPE_UNKNOWN,
- active: Boolean = false) {
+ fun addMode(
+ id: String,
+ @AutomaticZenRule.Type type: Int = AutomaticZenRule.TYPE_UNKNOWN,
+ active: Boolean = false,
+ ) {
mutableModesFlow.value += newMode(id, type, active)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index abc163867248..64a2de5025de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -31,7 +31,6 @@ import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import androidx.annotation.DrawableRes;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Random;
@@ -44,22 +43,7 @@ public class TestModeBuilder {
private boolean mIsManualDnd;
public static final ZenMode EXAMPLE = new TestModeBuilder().build();
-
- public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(
- INTERRUPTION_FILTER_PRIORITY, true);
-
- public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(
- INTERRUPTION_FILTER_PRIORITY, false);
-
- @NonNull
- public static ZenMode manualDnd(@NotificationManager.InterruptionFilter int filter,
- boolean isActive) {
- return new TestModeBuilder()
- .makeManualDnd()
- .setInterruptionFilter(filter)
- .setActive(isActive)
- .build();
- }
+ public static final ZenMode MANUAL_DND = new TestModeBuilder().makeManualDnd().build();
public TestModeBuilder() {
// Reasonable defaults
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 3b18aa310c91..4e821ca50dce 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.applications;
+import static android.content.pm.Flags.FLAG_REMOVE_HIDDEN_MODULE_USAGE;
import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
import static android.os.UserHandle.MU_ENABLED;
import static android.os.UserHandle.USER_SYSTEM;
@@ -59,6 +60,8 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
@@ -204,6 +207,7 @@ public class ApplicationsStateRoboTest {
info.setPackageName(packageName);
info.setApkInApexPackageNames(Collections.singletonList(apexPackageName));
// will treat any app with package name that contains "hidden" as hidden module
+ // TODO(b/382016780): to be removed after flag cleanup.
info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
return info;
}
@@ -414,6 +418,7 @@ public class ApplicationsStateRoboTest {
}
@Test
+ @DisableFlags({FLAG_REMOVE_HIDDEN_MODULE_USAGE})
public void onResume_shouldNotIncludeSystemHiddenModule() {
mSession.onResume();
@@ -424,6 +429,18 @@ public class ApplicationsStateRoboTest {
}
@Test
+ @EnableFlags({FLAG_REMOVE_HIDDEN_MODULE_USAGE})
+ public void onResume_shouldIncludeSystemModule() {
+ mSession.onResume();
+
+ final List<ApplicationInfo> mApplications = mApplicationsState.mApplications;
+ assertThat(mApplications).hasSize(3);
+ assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1");
+ assertThat(mApplications.get(1).packageName).isEqualTo("test.hidden.module.2");
+ assertThat(mApplications.get(2).packageName).isEqualTo("test.package.3");
+ }
+
+ @Test
public void removeAndInstall_noWorkprofile_doResumeIfNeededLocked_shouldClearEntries()
throws RemoteException {
// scenario: only owner user
@@ -832,6 +849,7 @@ public class ApplicationsStateRoboTest {
mApplicationsState.mEntriesMap.clear();
ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0);
mApplicationsState.mApplications.add(appInfo);
+ // TODO(b/382016780): to be removed after flag cleanup.
mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false);
assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
@@ -839,6 +857,7 @@ public class ApplicationsStateRoboTest {
}
@Test
+ @DisableFlags({FLAG_REMOVE_HIDDEN_MODULE_USAGE})
public void isHiddenModule_hasApkInApexInfo_shouldSupportHiddenApexPackage() {
mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
ApplicationsState.sInstance = null;
@@ -853,6 +872,7 @@ public class ApplicationsStateRoboTest {
}
@Test
+ @DisableFlags({FLAG_REMOVE_HIDDEN_MODULE_USAGE})
public void isHiddenModule_noApkInApexInfo_onlySupportHiddenModule() {
mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
ApplicationsState.sInstance = null;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 21dde1fd9411..a215464f66c2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -50,6 +50,9 @@ import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.os.Parcel;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.FeatureFlagUtils;
import androidx.test.core.app.ApplicationProvider;
@@ -72,6 +75,8 @@ import java.util.List;
public class HearingAidDeviceManagerTest {
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final long HISYNCID1 = 10;
private static final long HISYNCID2 = 11;
@@ -736,6 +741,7 @@ public class HearingAidDeviceManagerTest {
@Test
public void onActiveDeviceChanged_connected_callSetStrategies() {
+ when(mCachedDevice1.isConnectedHearingAidDevice()).thenReturn(true);
when(mHelper.getMatchedHearingDeviceAttributesForOutput(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
@@ -750,6 +756,7 @@ public class HearingAidDeviceManagerTest {
@Test
public void onActiveDeviceChanged_disconnected_callSetStrategiesWithAutoValue() {
+ when(mCachedDevice1.isConnectedHearingAidDevice()).thenReturn(false);
when(mHelper.getMatchedHearingDeviceAttributesForOutput(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
@@ -952,6 +959,38 @@ public class HearingAidDeviceManagerTest {
ConnectionStatus.CONNECTED);
}
+ @Test
+ @RequiresFlagsEnabled(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_INPUT_ROUTING_CONTROL)
+ public void onActiveDeviceChanged_activeHearingAidProfile_callSetInputDeviceForCalls() {
+ when(mCachedDevice1.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+ when(mDevice1.isMicrophonePreferredForCalls()).thenReturn(true);
+ doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),
+ anyInt());
+
+ mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
+
+ verify(mHelper).setPreferredInputDeviceForCalls(
+ eq(mCachedDevice1), eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
+
+ }
+
+ @Test
+ @RequiresFlagsEnabled(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_INPUT_ROUTING_CONTROL)
+ public void onActiveDeviceChanged_notActiveHearingAidProfile_callClearInputDeviceForCalls() {
+ when(mCachedDevice1.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
+ when(mDevice1.isMicrophonePreferredForCalls()).thenReturn(true);
+ doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),
+ anyInt());
+
+ mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
+
+ verify(mHelper).clearPreferredInputDeviceForCalls();
+ }
+
private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) {
return new HearingAidInfo.Builder()
.setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index d08d91d18b27..6b30f159129e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -87,7 +87,7 @@ public class ZenModeTest {
@Test
public void testBasicMethods_manualDnd() {
- ZenMode manualMode = TestModeBuilder.MANUAL_DND_INACTIVE;
+ ZenMode manualMode = TestModeBuilder.MANUAL_DND;
assertThat(manualMode.getId()).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
assertThat(manualMode.isManualDnd()).isTrue();
@@ -271,7 +271,7 @@ public class ZenModeTest {
@Test
public void setInterruptionFilter_manualDnd_throws() {
- ZenMode manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE;
+ ZenMode manualDnd = TestModeBuilder.MANUAL_DND;
assertThrows(IllegalStateException.class,
() -> manualDnd.setInterruptionFilter(INTERRUPTION_FILTER_ALL));
@@ -280,24 +280,46 @@ public class ZenModeTest {
@Test
public void canEditPolicy_onlyFalseForSpecialDnd() {
assertThat(TestModeBuilder.EXAMPLE.canEditPolicy()).isTrue();
- assertThat(TestModeBuilder.MANUAL_DND_ACTIVE.canEditPolicy()).isTrue();
- assertThat(TestModeBuilder.MANUAL_DND_INACTIVE.canEditPolicy()).isTrue();
- ZenMode dndWithAlarms = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, true);
+ ZenMode inactiveDnd = new TestModeBuilder().makeManualDnd().setActive(false).build();
+ assertThat(inactiveDnd.canEditPolicy()).isTrue();
+
+ ZenMode activeDnd = new TestModeBuilder().makeManualDnd().setActive(true).build();
+ assertThat(activeDnd.canEditPolicy()).isTrue();
+
+ ZenMode dndWithAlarms = new TestModeBuilder()
+ .makeManualDnd()
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .setActive(true)
+ .build();
assertThat(dndWithAlarms.canEditPolicy()).isFalse();
- ZenMode dndWithNone = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, true);
+
+ ZenMode dndWithNone = new TestModeBuilder()
+ .makeManualDnd()
+ .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setActive(true)
+ .build();
assertThat(dndWithNone.canEditPolicy()).isFalse();
// Note: Backend will never return an inactive manual mode with custom filter.
- ZenMode badDndWithAlarms = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, false);
+ ZenMode badDndWithAlarms = new TestModeBuilder()
+ .makeManualDnd()
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .setActive(false)
+ .build();
assertThat(badDndWithAlarms.canEditPolicy()).isFalse();
- ZenMode badDndWithNone = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, false);
+
+ ZenMode badDndWithNone = new TestModeBuilder()
+ .makeManualDnd()
+ .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setActive(false)
+ .build();
assertThat(badDndWithNone.canEditPolicy()).isFalse();
}
@Test
public void canEditPolicy_whenTrue_allowsSettingPolicyAndEffects() {
- ZenMode normalDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_PRIORITY, true);
+ ZenMode normalDnd = new TestModeBuilder().makeManualDnd().setActive(true).build();
assertThat(normalDnd.canEditPolicy()).isTrue();
@@ -313,7 +335,11 @@ public class ZenModeTest {
@Test
public void canEditPolicy_whenFalse_preventsSettingFilterPolicyOrEffects() {
- ZenMode specialDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, true);
+ ZenMode specialDnd = new TestModeBuilder()
+ .makeManualDnd()
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .setActive(true)
+ .build();
assertThat(specialDnd.canEditPolicy()).isFalse();
assertThrows(IllegalStateException.class,
@@ -324,7 +350,7 @@ public class ZenModeTest {
@Test
public void comparator_prioritizes() {
- ZenMode manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE;
+ ZenMode manualDnd = TestModeBuilder.MANUAL_DND;
ZenMode driving1 = new TestModeBuilder().setName("b1").setType(TYPE_DRIVING).build();
ZenMode driving2 = new TestModeBuilder().setName("b2").setType(TYPE_DRIVING).build();
ZenMode bedtime1 = new TestModeBuilder().setName("c1").setType(TYPE_BEDTIME).build();
@@ -403,7 +429,7 @@ public class ZenModeTest {
@Test
public void getIconKey_manualDnd_isDndIcon() {
- ZenIcon.Key iconKey = TestModeBuilder.MANUAL_DND_INACTIVE.getIconKey();
+ ZenIcon.Key iconKey = TestModeBuilder.MANUAL_DND.getIconKey();
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index dd28402d705f..7b4a2ca5de39 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -155,6 +155,7 @@ public class SecureSettings {
Settings.Secure.SCREENSAVER_COMPONENTS,
Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b01f6229af16..b0309a8fa5a5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -230,6 +230,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.SCREENSAVER_COMPONENTS, COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR);
VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_DOCK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index f79a60f5be96..c1c3e04d46fd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -212,6 +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_RESTORE_WIFI_CONFIG =
+ "failed_to_restore_wifi_config";
// Name of the temporary file we use during full backup/restore. This is
@@ -1455,8 +1457,14 @@ public class SettingsBackupAgent extends BackupAgentHelper {
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() {
@@ -1468,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) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 5ad4b8a6dffe..1c6d6816e9b4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2550,6 +2550,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
SecureSettingsProto.Screensaver.DEFAULT_COMPONENT);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ SecureSettingsProto.Screensaver.ACTIVATE_ON_POSTURED);
p.end(screensaverToken);
final long searchToken = p.start(SecureSettingsProto.SEARCH);
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/Android.bp b/packages/SystemUI/Android.bp
index 9adc95a01216..6b2449fdaa49 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -92,7 +92,6 @@ filegroup {
"tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt",
"tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt",
- "tests/src/**/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt",
"tests/src/**/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt",
"tests/src/**/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt",
"tests/src/**/systemui/education/domain/ui/view/ContextualEduDialogTest.kt",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7d5fd903c01b..70d4cc2e4e26 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -424,24 +424,6 @@ flag {
}
flag {
- name: "status_bar_use_repos_for_call_chip"
- namespace: "systemui"
- description: "Use repositories as the source of truth for call notifications shown as a chip in"
- "the status bar"
- bug: "328584859"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "status_bar_call_chip_notification_icon"
- namespace: "systemui"
- description: "Use the small icon set on the notification for the status bar call chip"
- bug: "354930838"
-}
-
-flag {
name: "status_bar_signal_policy_refactor"
namespace: "systemui"
description: "Use a settings observer for airplane mode and make StatusBarSignalPolicy startable"
@@ -1344,6 +1326,13 @@ flag {
}
flag {
+ name: "output_switcher_redesign"
+ namespace: "systemui"
+ description: "Enables visual update for Media Output Switcher"
+ bug: "388296370"
+}
+
+flag {
namespace: "systemui"
name: "enable_view_capture_tracing"
description: "Enables view capture tracing in System UI."
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 c7d6e8aed3b4..96401ce6e1c7 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
@@ -147,21 +147,66 @@ private data class NestedDraggableElement(
private val orientation: Orientation,
private val overscrollEffect: OverscrollEffect?,
private val enabled: Boolean,
-) : ModifierNodeElement<NestedDraggableNode>() {
- override fun create(): NestedDraggableNode {
- return NestedDraggableNode(draggable, orientation, overscrollEffect, enabled)
+) : ModifierNodeElement<NestedDraggableRootNode>() {
+ override fun create(): NestedDraggableRootNode {
+ return NestedDraggableRootNode(draggable, orientation, overscrollEffect, enabled)
}
- override fun update(node: NestedDraggableNode) {
+ override fun update(node: NestedDraggableRootNode) {
node.update(draggable, orientation, overscrollEffect, enabled)
}
}
+/**
+ * A root node on top of [NestedDraggableNode] so that no [PointerInputModifierNode] is installed
+ * when this draggable is disabled.
+ */
+private class NestedDraggableRootNode(
+ draggable: NestedDraggable,
+ orientation: Orientation,
+ overscrollEffect: OverscrollEffect?,
+ enabled: Boolean,
+) : DelegatingNode() {
+ private var delegateNode =
+ if (enabled) create(draggable, orientation, overscrollEffect) else null
+
+ fun update(
+ draggable: NestedDraggable,
+ orientation: Orientation,
+ overscrollEffect: OverscrollEffect?,
+ enabled: Boolean,
+ ) {
+ // Disabled.
+ if (!enabled) {
+ delegateNode?.let { undelegate(it) }
+ delegateNode = null
+ return
+ }
+
+ // Disabled => Enabled.
+ val nullableDelegate = delegateNode
+ if (nullableDelegate == null) {
+ delegateNode = create(draggable, orientation, overscrollEffect)
+ return
+ }
+
+ // Enabled => Enabled (update).
+ nullableDelegate.update(draggable, orientation, overscrollEffect)
+ }
+
+ private fun create(
+ draggable: NestedDraggable,
+ orientation: Orientation,
+ overscrollEffect: OverscrollEffect?,
+ ): NestedDraggableNode {
+ return delegate(NestedDraggableNode(draggable, orientation, overscrollEffect))
+ }
+}
+
private class NestedDraggableNode(
private var draggable: NestedDraggable,
override var orientation: Orientation,
private var overscrollEffect: OverscrollEffect?,
- private var enabled: Boolean,
) :
DelegatingNode(),
PointerInputModifierNode,
@@ -169,23 +214,11 @@ 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) }
- field = value?.also { delegate(it) }
- }
-
- private var detectDragsDelegate: SuspendingPointerInputModifierNode? = null
- set(value) {
- field?.let { undelegate(it) }
- field = value?.also { delegate(it) }
- }
+ private val trackWheelScroll =
+ delegate(SuspendingPointerInputModifierNode { trackWheelScroll() })
+ private val trackDownPositionDelegate =
+ delegate(SuspendingPointerInputModifierNode { trackDownPosition() })
+ private val detectDragsDelegate = delegate(SuspendingPointerInputModifierNode { detectDrags() })
/** The controller created by the nested scroll logic (and *not* the drag logic). */
private var nestedScrollController: NestedScrollController? = null
@@ -214,26 +247,25 @@ private class NestedDraggableNode(
draggable: NestedDraggable,
orientation: Orientation,
overscrollEffect: OverscrollEffect?,
- enabled: Boolean,
) {
+ if (
+ draggable == this.draggable &&
+ orientation == this.orientation &&
+ overscrollEffect == this.overscrollEffect
+ ) {
+ return
+ }
+
this.draggable = draggable
this.orientation = orientation
this.overscrollEffect = overscrollEffect
- this.enabled = enabled
- trackDownPositionDelegate?.resetPointerInputHandler()
- detectDragsDelegate?.resetPointerInputHandler()
+ trackWheelScroll.resetPointerInputHandler()
+ trackDownPositionDelegate.resetPointerInputHandler()
+ detectDragsDelegate.resetPointerInputHandler()
+
nestedScrollController?.ensureOnDragStoppedIsCalled()
nestedScrollController = null
-
- if (!enabled && trackWheelScroll != null) {
- check(trackDownPositionDelegate != null)
- check(detectDragsDelegate != null)
-
- trackWheelScroll = null
- trackDownPositionDelegate = null
- detectDragsDelegate = null
- }
}
override fun onPointerEvent(
@@ -241,26 +273,15 @@ private class NestedDraggableNode(
pass: PointerEventPass,
bounds: IntSize,
) {
- if (!enabled) return
-
- 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)
+ trackWheelScroll.onPointerEvent(pointerEvent, pass, bounds)
+ trackDownPositionDelegate.onPointerEvent(pointerEvent, pass, bounds)
+ detectDragsDelegate.onPointerEvent(pointerEvent, pass, bounds)
}
override fun onCancelPointerInput() {
- trackWheelScroll?.onCancelPointerInput()
- trackDownPositionDelegate?.onCancelPointerInput()
- detectDragsDelegate?.onCancelPointerInput()
+ trackWheelScroll.onCancelPointerInput()
+ trackDownPositionDelegate.onCancelPointerInput()
+ detectDragsDelegate.onCancelPointerInput()
}
/*
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 f9cf495d9d9f..5de0f1221f0f 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
@@ -25,11 +25,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -37,10 +40,14 @@ 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.platform.testTag
import androidx.compose.ui.test.ScrollWheel
+import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performMouseInput
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeDown
@@ -693,6 +700,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
}
@Test
+ @Ignore("b/388507816: re-enable this when the crash in HitPath is fixed")
fun pointersDown_clearedWhenDisabled() {
val draggable = TestDraggable()
var enabled by mutableStateOf(true)
@@ -740,6 +748,31 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
assertThat(draggable.onDragStartedCalled).isFalse()
}
+ @Test
+ fun doesNotConsumeGesturesWhenDisabled() {
+ val buttonTag = "button"
+ rule.setContent {
+ Box {
+ var count by remember { mutableStateOf(0) }
+ Button(onClick = { count++ }, Modifier.testTag(buttonTag).align(Alignment.Center)) {
+ Text("Count: $count")
+ }
+
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(remember { TestDraggable() }, orientation, enabled = false)
+ )
+ }
+ }
+
+ rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 0")
+
+ // Click on the root at its center, where the button is located. Clicks should go through
+ // the draggable and reach the button given that it is disabled.
+ repeat(3) { rule.onRoot().performClick() }
+ rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 3")
+ }
+
private fun ComposeContentTestRule.setContentWithTouchSlop(
content: @Composable () -> Unit
): Float {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9c53afecad11..a2a91fcd5d52 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -78,6 +78,18 @@ object TransitionDuration {
const val EDIT_MODE_TO_HUB_GRID_END_MS =
EDIT_MODE_TO_HUB_GRID_DELAY_MS + EDIT_MODE_TO_HUB_CONTENT_MS
const val HUB_TO_EDIT_MODE_CONTENT_MS = 250
+ const val TO_GLANCEABLE_HUB_DURATION_MS = 1000
+}
+
+val sceneTransitionsV2 = transitions {
+ to(CommunalScenes.Communal) {
+ spec = tween(durationMillis = TransitionDuration.TO_GLANCEABLE_HUB_DURATION_MS)
+ fade(AllElements)
+ }
+ to(CommunalScenes.Blank) {
+ spec = tween(durationMillis = TO_GONE_DURATION.toInt(DurationUnit.MILLISECONDS))
+ fade(AllElements)
+ }
}
val sceneTransitions = transitions {
@@ -157,7 +169,7 @@ fun CommunalContainer(
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
canChangeScene = { _ -> viewModel.canChangeScene() },
- transitions = sceneTransitions,
+ transitions = if (viewModel.v2FlagEnabled()) sceneTransitionsV2 else sceneTransitions,
)
}
val isUiBlurred by viewModel.isUiBlurred.collectAsStateWithLifecycle()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt
index 93c10b6224ab..6ca9c7934979 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt
@@ -17,17 +17,12 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.communal.ui.compose.AllElements
-import com.android.systemui.communal.ui.compose.Communal
fun TransitionBuilder.dreamToCommunalTransition() {
spec = tween(durationMillis = 1000)
- // Translate communal hub grid from the end direction.
- translate(Communal.Elements.Grid, Edge.End)
-
- // Fade all communal hub elements.
- timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
+ // Fade in all communal hub elements.
+ fade(AllElements)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
index 826a2550cca3..de9a78c497f8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
@@ -17,21 +17,12 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.communal.ui.compose.AllElements
-import com.android.systemui.communal.ui.compose.Communal
-import com.android.systemui.scene.shared.model.Scenes
fun TransitionBuilder.lockscreenToCommunalTransition() {
spec = tween(durationMillis = 1000)
- // Translate lockscreen to the start direction.
- translate(Scenes.Lockscreen.rootElementKey, Edge.Start)
-
- // Translate communal hub grid from the end direction.
- translate(Communal.Elements.Grid, Edge.End)
-
- // Fade all communal hub elements.
- timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
+ // Fade all communal hub elements in.
+ fade(AllElements)
}
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 b41c55858c75..2ca846424d93 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
@@ -429,6 +429,58 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol
}
/**
+ * Finds the best matching [UserActionResult] for the given [swipe] within this [Content].
+ * Prioritizes actions with matching [Swipe.Resolved.fromSource].
+ *
+ * @param swipe The swipe to match against.
+ * @return The best matching [UserActionResult], or `null` if no match is found.
+ */
+ private fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? {
+ if (!areSwipesAllowed()) {
+ return null
+ }
+
+ var bestPoints = Int.MIN_VALUE
+ var bestMatch: UserActionResult? = null
+ userActions.forEach { (actionSwipe, actionResult) ->
+ if (
+ actionSwipe !is Swipe.Resolved ||
+ // The direction must match.
+ actionSwipe.direction != swipe.direction ||
+ // The number of pointers down must match.
+ actionSwipe.pointerCount != swipe.pointerCount ||
+ // The action requires a specific fromSource.
+ (actionSwipe.fromSource != null &&
+ actionSwipe.fromSource != swipe.fromSource) ||
+ // The action requires a specific pointerType.
+ (actionSwipe.pointersType != null &&
+ actionSwipe.pointersType != swipe.pointersType)
+ ) {
+ // This action is not eligible.
+ return@forEach
+ }
+
+ val sameFromSource = actionSwipe.fromSource == swipe.fromSource
+ val samePointerType = actionSwipe.pointersType == swipe.pointersType
+ // Prioritize actions with a perfect match.
+ if (sameFromSource && samePointerType) {
+ return actionResult
+ }
+
+ var points = 0
+ if (sameFromSource) points++
+ if (samePointerType) points++
+
+ // Otherwise, keep track of the best eligible action.
+ if (points > bestPoints) {
+ bestPoints = points
+ bestMatch = actionResult
+ }
+ }
+ return bestMatch
+ }
+
+ /**
* Update the swipes results.
*
* Usually we don't want to update them while doing a drag, because this could change the target
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 3f6bce724b1b..e2212113404d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -37,11 +37,7 @@ internal fun Modifier.swipeToScene(
draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
): Modifier {
- return if (draggableHandler.enabled()) {
- this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
- } else {
- this
- }
+ return then(SwipeToSceneElement(draggableHandler, swipeDetector, draggableHandler.enabled()))
}
private fun DraggableHandlerImpl.enabled(): Boolean {
@@ -61,84 +57,62 @@ internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation }
}
-/**
- * Finds the best matching [UserActionResult] for the given [swipe] within this [Content].
- * Prioritizes actions with matching [Swipe.Resolved.fromSource].
- *
- * @param swipe The swipe to match against.
- * @return The best matching [UserActionResult], or `null` if no match is found.
- */
-internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? {
- if (!areSwipesAllowed()) {
- return null
- }
-
- var bestPoints = Int.MIN_VALUE
- var bestMatch: UserActionResult? = null
- userActions.forEach { (actionSwipe, actionResult) ->
- if (
- actionSwipe !is Swipe.Resolved ||
- // The direction must match.
- actionSwipe.direction != swipe.direction ||
- // The number of pointers down must match.
- actionSwipe.pointerCount != swipe.pointerCount ||
- // The action requires a specific fromSource.
- (actionSwipe.fromSource != null && actionSwipe.fromSource != swipe.fromSource) ||
- // The action requires a specific pointerType.
- (actionSwipe.pointersType != null && actionSwipe.pointersType != swipe.pointersType)
- ) {
- // This action is not eligible.
- return@forEach
- }
-
- val sameFromSource = actionSwipe.fromSource == swipe.fromSource
- val samePointerType = actionSwipe.pointersType == swipe.pointersType
- // Prioritize actions with a perfect match.
- if (sameFromSource && samePointerType) {
- return actionResult
- }
-
- var points = 0
- if (sameFromSource) points++
- if (samePointerType) points++
-
- // Otherwise, keep track of the best eligible action.
- if (points > bestPoints) {
- bestPoints = points
- bestMatch = actionResult
- }
- }
- return bestMatch
-}
-
private data class SwipeToSceneElement(
val draggableHandler: DraggableHandlerImpl,
val swipeDetector: SwipeDetector,
+ val enabled: Boolean,
) : ModifierNodeElement<SwipeToSceneRootNode>() {
override fun create(): SwipeToSceneRootNode =
- SwipeToSceneRootNode(draggableHandler, swipeDetector)
+ SwipeToSceneRootNode(draggableHandler, swipeDetector, enabled)
override fun update(node: SwipeToSceneRootNode) {
- node.update(draggableHandler, swipeDetector)
+ node.update(draggableHandler, swipeDetector, enabled)
}
}
private class SwipeToSceneRootNode(
draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
+ enabled: Boolean,
) : DelegatingNode() {
- private var delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ private var delegateNode = if (enabled) create(draggableHandler, swipeDetector) else null
+
+ fun update(
+ draggableHandler: DraggableHandlerImpl,
+ swipeDetector: SwipeDetector,
+ enabled: Boolean,
+ ) {
+ // Disabled.
+ if (!enabled) {
+ delegateNode?.let { undelegate(it) }
+ delegateNode = null
+ return
+ }
+
+ // Disabled => Enabled.
+ val nullableDelegate = delegateNode
+ if (nullableDelegate == null) {
+ delegateNode = create(draggableHandler, swipeDetector)
+ return
+ }
- fun update(draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector) {
- if (draggableHandler == delegateNode.draggableHandler) {
+ // Enabled => Enabled (update).
+ if (draggableHandler == nullableDelegate.draggableHandler) {
// Simple update, just update the swipe detector directly and keep the node.
- delegateNode.swipeDetector = swipeDetector
+ nullableDelegate.swipeDetector = swipeDetector
} else {
// The draggableHandler changed, force recreate the underlying SwipeToSceneNode.
- undelegate(delegateNode)
- delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ undelegate(nullableDelegate)
+ delegateNode = create(draggableHandler, swipeDetector)
}
}
+
+ private fun create(
+ draggableHandler: DraggableHandlerImpl,
+ swipeDetector: SwipeDetector,
+ ): SwipeToSceneNode {
+ return delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ }
}
private class SwipeToSceneNode(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 9135fdd15b3a..e80805a4e374 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -936,4 +936,45 @@ class SwipeToSceneTest {
assertThat(state.transitionState).isIdle()
assertThat(state.transitionState).hasCurrentScene(SceneC)
}
+
+ @Test
+ fun swipeToSceneNodeIsKeptWhenDisabled() {
+ var hasHorizontalActions by mutableStateOf(false)
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state) {
+ scene(
+ SceneA,
+ userActions =
+ buildList {
+ add(Swipe.Down to SceneB)
+
+ if (hasHorizontalActions) {
+ add(Swipe.Left to SceneC)
+ }
+ }
+ .toMap(),
+ ) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ // Swipe down to start a transition to B.
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+
+ assertThat(state.transitionState).isSceneTransition()
+
+ // Add new horizontal user actions. This should not stop the current transition, even if a
+ // new horizontal Modifier.swipeToScene() handler is introduced where the vertical one was.
+ hasHorizontalActions = true
+ rule.waitForIdle()
+ assertThat(state.transitionState).isSceneTransition()
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index aed3a2df0436..e69fa994931d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -50,7 +50,6 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
DEFAULT_CLOCK_ID,
clockCtx.resources.getString(R.string.clock_default_name),
clockCtx.resources.getString(R.string.clock_default_description),
- isReactiveToTone = true,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
index a11dace0505c..4c329dcf2f2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -18,12 +18,20 @@ package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.content.ContentResolver
+import android.content.applicationContext
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothEventManager
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.bluetooth.VolumeControlProfile
+import com.android.settingslib.volume.shared.AudioSharingLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -38,10 +46,16 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -50,8 +64,11 @@ import org.mockito.kotlin.any
class AudioSharingInteractorTest : SysuiTestCase() {
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos()
+
@Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+
@Mock private lateinit var bluetoothLeBroadcastMetadata: BluetoothLeBroadcastMetadata
+
@Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
private lateinit var underTest: AudioSharingInteractor
@@ -157,13 +174,15 @@ class AudioSharingInteractorTest : SysuiTestCase() {
fun testHandleAudioSourceWhenReady_hasProfileButAudioSharingOff_sourceNotAdded() =
with(kosmos) {
testScope.runTest {
- bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
localBluetoothLeBroadcast
)
val job = launch { underTest.handleAudioSourceWhenReady() }
runCurrent()
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
+ runCurrent()
assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
job.cancel()
@@ -174,15 +193,14 @@ class AudioSharingInteractorTest : SysuiTestCase() {
fun testHandleAudioSourceWhenReady_audioSharingOnButNoPlayback_sourceNotAdded() =
with(kosmos) {
testScope.runTest {
- bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
localBluetoothLeBroadcast
)
val job = launch { underTest.handleAudioSourceWhenReady() }
runCurrent()
- verify(localBluetoothLeBroadcast)
- .registerServiceCallBack(any(), callbackCaptor.capture())
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
runCurrent()
assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
@@ -194,13 +212,15 @@ class AudioSharingInteractorTest : SysuiTestCase() {
fun testHandleAudioSourceWhenReady_audioSharingOnAndPlaybackStarts_sourceAdded() =
with(kosmos) {
testScope.runTest {
- bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
localBluetoothLeBroadcast
)
val job = launch { underTest.handleAudioSourceWhenReady() }
runCurrent()
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ runCurrent()
verify(localBluetoothLeBroadcast)
.registerServiceCallBack(any(), callbackCaptor.capture())
runCurrent()
@@ -211,4 +231,100 @@ class AudioSharingInteractorTest : SysuiTestCase() {
job.cancel()
}
}
+
+ @Test
+ fun testHandleAudioSourceWhenReady_skipInitialValue_noAudioSharing_sourceNotAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ val (broadcast, repository) = setupRepositoryImpl()
+ val interactor =
+ object :
+ AudioSharingInteractorImpl(
+ applicationContext,
+ localBluetoothManager,
+ repository,
+ testDispatcher,
+ ) {
+ override suspend fun audioSharingAvailable() = true
+ }
+ val job = launch { interactor.handleAudioSourceWhenReady() }
+ runCurrent()
+ // Verify callback registered for onBroadcastStartedOrStopped
+ verify(broadcast).registerServiceCallBack(any(), callbackCaptor.capture())
+ runCurrent()
+ // Verify source is not added
+ verify(repository, never()).addSource()
+ job.cancel()
+ }
+ }
+
+ @Test
+ fun testHandleAudioSourceWhenReady_skipInitialValue_newAudioSharing_sourceAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ val (broadcast, repository) = setupRepositoryImpl()
+ val interactor =
+ object :
+ AudioSharingInteractorImpl(
+ applicationContext,
+ localBluetoothManager,
+ repository,
+ testDispatcher,
+ ) {
+ override suspend fun audioSharingAvailable() = true
+ }
+ val job = launch { interactor.handleAudioSourceWhenReady() }
+ runCurrent()
+ // Verify callback registered for onBroadcastStartedOrStopped
+ verify(broadcast).registerServiceCallBack(any(), callbackCaptor.capture())
+ // Audio sharing started, trigger onBroadcastStarted
+ whenever(broadcast.isEnabled(null)).thenReturn(true)
+ callbackCaptor.value.onBroadcastStarted(0, 0)
+ runCurrent()
+ // Verify callback registered for onBroadcastMetadataChanged
+ verify(broadcast, times(2)).registerServiceCallBack(any(), callbackCaptor.capture())
+ runCurrent()
+ // Trigger onBroadcastMetadataChanged (ready to add source)
+ callbackCaptor.value.onBroadcastMetadataChanged(0, bluetoothLeBroadcastMetadata)
+ runCurrent()
+ // Verify source added
+ verify(repository).addSource()
+ job.cancel()
+ }
+ }
+
+ private fun setupRepositoryImpl(): Pair<LocalBluetoothLeBroadcast, AudioSharingRepositoryImpl> {
+ with(kosmos) {
+ val broadcast =
+ mock<LocalBluetoothLeBroadcast> {
+ on { isProfileReady } doReturn true
+ on { isEnabled(null) } doReturn false
+ }
+ val assistant =
+ mock<LocalBluetoothLeBroadcastAssistant> { on { isProfileReady } doReturn true }
+ val volumeControl = mock<VolumeControlProfile> { on { isProfileReady } doReturn true }
+ val profileManager =
+ mock<LocalBluetoothProfileManager> {
+ on { leAudioBroadcastProfile } doReturn broadcast
+ on { leAudioBroadcastAssistantProfile } doReturn assistant
+ on { volumeControlProfile } doReturn volumeControl
+ }
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(localBluetoothManager.eventManager).thenReturn(mock<BluetoothEventManager> {})
+
+ val repository =
+ AudioSharingRepositoryImpl(
+ localBluetoothManager,
+ com.android.settingslib.volume.data.repository.AudioSharingRepositoryImpl(
+ mock<ContentResolver> {},
+ localBluetoothManager,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ mock<AudioSharingLogger> {},
+ ),
+ testDispatcher,
+ )
+ return Pair(broadcast, spy(repository))
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
index acfe9dd45f75..f0746064f67f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
@@ -111,6 +111,28 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
}
@Test
+ fun testStopAudioSharing() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
+ audioSharingRepository.setAudioSharingAvailable(true)
+ underTest.stopAudioSharing()
+ verify(leAudioBroadcastProfile).stopLatestBroadcast()
+ }
+ }
+
+ @Test
+ fun testStopAudioSharing_flagOff_doNothing() =
+ with(kosmos) {
+ testScope.runTest {
+ audioSharingRepository.setAudioSharingAvailable(false)
+ underTest.stopAudioSharing()
+ verify(leAudioBroadcastProfile, never()).stopLatestBroadcast()
+ }
+ }
+
+ @Test
fun testAddSource_flagOff_doesNothing() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 44f9720cb9e4..ad0337e5ce86 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -15,14 +15,15 @@
*/
package com.android.systemui.bluetooth.qsdialog
-import androidx.test.ext.junit.runners.AndroidJUnit4
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -48,6 +49,7 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
private lateinit var notConnectedDeviceItem: DeviceItem
private lateinit var connectedMediaDeviceItem: DeviceItem
private lateinit var connectedOtherDeviceItem: DeviceItem
+ private lateinit var audioSharingDeviceItem: DeviceItem
@Mock private lateinit var dialog: SystemUIDialog
@Before
@@ -59,7 +61,7 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
- background = null
+ background = null,
)
notConnectedDeviceItem =
DeviceItem(
@@ -68,7 +70,7 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
- background = null
+ background = null,
)
connectedMediaDeviceItem =
DeviceItem(
@@ -77,7 +79,7 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
- background = null
+ background = null,
)
connectedOtherDeviceItem =
DeviceItem(
@@ -86,7 +88,16 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
- background = null
+ background = null,
+ )
+ audioSharingDeviceItem =
+ DeviceItem(
+ type = DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
+ deviceName = DEVICE_NAME,
+ connectionSummary = DEVICE_CONNECTION_SUMMARY,
+ iconWithDescription = null,
+ background = null,
)
actionInteractorImpl = kosmos.deviceItemActionInteractorImpl
}
@@ -135,6 +146,29 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun onActionIconClick_onIntent() {
+ with(kosmos) {
+ testScope.runTest {
+ var onIntentCalledOnAddress = ""
+ whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
+ actionInteractorImpl.onActionIconClick(connectedMediaDeviceItem) {
+ onIntentCalledOnAddress = connectedMediaDeviceItem.cachedBluetoothDevice.address
+ }
+ assertThat(onIntentCalledOnAddress).isEqualTo(DEVICE_ADDRESS)
+ }
+ }
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun onActionIconClick_audioSharingDeviceType_throwException() {
+ with(kosmos) {
+ testScope.runTest {
+ actionInteractorImpl.onActionIconClick(audioSharingDeviceItem) {}
+ }
+ }
+ }
+
private companion object {
const val DEVICE_NAME = "device"
const val DEVICE_CONNECTION_SUMMARY = "active"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 20d66155e5ca..6c955bf1818d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -256,6 +256,22 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
@EnableSceneContainer
@Test
+ fun playSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() =
+ testScope.runTest {
+ underTest = kosmos.deviceEntryHapticsInteractor
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+
+ enrollFace()
+ kosmos.configureKeyguardBypass(isBypassAvailable = false)
+ runCurrent()
+ configureDeviceEntryFromBiometricSource(isFaceUnlock = true, bypassEnabled = false)
+ kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true
+
+ assertThat(playSuccessHaptic).isNotNull()
+ }
+
+ @EnableSceneContainer
+ @Test
fun skipSuccessHaptic_onDeviceEntryFromSfps_whenPowerDown_sceneContainer() =
testScope.runTest {
kosmos.configureKeyguardBypass(isBypassAvailable = false)
@@ -299,6 +315,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
private fun configureDeviceEntryFromBiometricSource(
isFpUnlock: Boolean = false,
isFaceUnlock: Boolean = false,
+ bypassEnabled: Boolean = true,
) {
// Mock DeviceEntrySourceInteractor#deviceEntryBiometricAuthSuccessState
if (isFpUnlock) {
@@ -314,11 +331,14 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
)
// Mock DeviceEntrySourceInteractor#faceWakeAndUnlockMode = MODE_UNLOCK_COLLAPSING
- kosmos.sceneInteractor.setTransitionState(
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(Scenes.Lockscreen)
+ // if the successful face authentication will bypass keyguard
+ if (bypassEnabled) {
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Lockscreen)
+ )
)
- )
+ }
}
underTest = kosmos.deviceEntryHapticsInteractor
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
index 74e8257f4f08..5e023a203267 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
@@ -292,8 +292,7 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
assertThat(model?.signalCount).isEqualTo(originalValue + 1)
}
@@ -306,8 +305,7 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
assertThat(model?.signalCount).isEqualTo(originalValue)
}
@@ -321,8 +319,7 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
assertThat(model?.signalCount).isEqualTo(originalValue + 1)
}
@@ -335,8 +332,7 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
assertThat(model?.signalCount).isEqualTo(originalValue)
}
@@ -347,8 +343,7 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
assertThat(model?.lastShortcutTriggeredTime).isNull()
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ true, gestureType)
+ updateContextualEduStats(/* isTrackpadGesture= */ true, gestureType)
assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
}
@@ -358,15 +353,14 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
testScope.runTest {
setUpForDeviceConnection()
tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
- DeviceType.TOUCHPAD,
+ getTargetDevice(gestureType),
eduClock.instant(),
)
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
eduClock.offset(initialDelayElapsedDuration)
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
assertThat(model?.signalCount).isEqualTo(originalValue + 1)
}
@@ -376,33 +370,92 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
testScope.runTest {
setUpForDeviceConnection()
tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
- DeviceType.TOUCHPAD,
+ getTargetDevice(gestureType),
eduClock.instant(),
)
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
// No offset to the clock to simulate update before initial delay
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
assertThat(model?.signalCount).isEqualTo(originalValue)
}
@Test
- fun dataUnchangedOnIncrementSignalCountWithoutOobeLaunchTime() =
+ fun dataUnchangedOnIncrementSignalCountWithoutOobeLaunchOrNotifyTime() =
testScope.runTest {
- // No update to OOBE launch time to simulate no OOBE is launched yet
+ // No update to OOBE launch/notify time to simulate no OOBE is launched yet
setUpForDeviceConnection()
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
assertThat(model?.signalCount).isEqualTo(originalValue)
}
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterNotifyTimeDelayWithoutLaunchTime() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ tutorialSchedulerRepository.setNotifiedTime(
+ getTargetDevice(gestureType),
+ eduClock.instant(),
+ )
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ eduClock.offset(initialDelayElapsedDuration)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountBeforeLaunchTimeDelayWithNotifyTime() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ tutorialSchedulerRepository.setNotifiedTime(
+ getTargetDevice(gestureType),
+ eduClock.instant(),
+ )
+ eduClock.offset(initialDelayElapsedDuration)
+
+ tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
+ getTargetDevice(gestureType),
+ eduClock.instant(),
+ )
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ // No offset to the clock to simulate update before initial delay of launch time
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterLaunchTimeDelayWithNotifyTime() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ tutorialSchedulerRepository.setNotifiedTime(
+ getTargetDevice(gestureType),
+ eduClock.instant(),
+ )
+ eduClock.offset(initialDelayElapsedDuration)
+
+ tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
+ getTargetDevice(gestureType),
+ eduClock.instant(),
+ )
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ eduClock.offset(initialDelayElapsedDuration)
+ updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
private suspend fun setUpForInitialDelayElapse() {
tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
DeviceType.TOUCHPAD,
@@ -465,12 +518,18 @@ class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: Ge
keyboardRepository.setIsAnyKeyboardConnected(true)
}
- private fun getOverviewProxyListener(): OverviewProxyListener {
+ private fun updateContextualEduStats(isTrackpadGesture: Boolean, gestureType: GestureType) {
val listenerCaptor = argumentCaptor<OverviewProxyListener>()
verify(overviewProxyService).addCallback(listenerCaptor.capture())
- return listenerCaptor.firstValue
+ listenerCaptor.firstValue.updateContextualEduStats(isTrackpadGesture, gestureType)
}
+ private fun getTargetDevice(gestureType: GestureType) =
+ when (gestureType) {
+ ALL_APPS -> DeviceType.KEYBOARD
+ else -> DeviceType.TOUCHPAD
+ }
+
companion object {
private val USER_INFOS = listOf(UserInfo(101, "Second User", 0))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 692b9c67f322..692b9c67f322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
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/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index fde9b8ce6a50..bf49186a7f01 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -28,7 +28,7 @@ import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.EnableZenModeDialog
-import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
@@ -187,7 +187,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
testScope.runTest {
val currentModes by collectLastValue(zenModeRepository.modes)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ zenModeRepository.activateMode(MANUAL_DND)
secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -2)
collectLastValue(underTest.lockScreenState)
runCurrent()
@@ -233,7 +233,6 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
testScope.runTest {
val currentModes by collectLastValue(zenModeRepository.modes)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER)
collectLastValue(underTest.lockScreenState)
runCurrent()
@@ -278,7 +277,6 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
fun onTriggered_dndModeIsOff_settingNotFOREVERorPROMPT_dndWithDuration() =
testScope.runTest {
val currentModes by collectLastValue(zenModeRepository.modes)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -900)
runCurrent()
@@ -323,7 +321,6 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
fun onTriggered_dndModeIsOff_settingIsPROMPT_showDialog() =
testScope.runTest {
val expandable: Expandable = mock()
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
collectLastValue(underTest.lockScreenState)
@@ -405,10 +402,6 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
testScope.runTest {
val lockScreenState by collectLastValue(underTest.lockScreenState)
- val manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE
- zenModeRepository.addMode(manualDnd)
- runCurrent()
-
assertThat(lockScreenState)
.isEqualTo(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
@@ -420,7 +413,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
)
)
- zenModeRepository.activateMode(manualDnd)
+ zenModeRepository.activateMode(MANUAL_DND)
runCurrent()
assertThat(lockScreenState)
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/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/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index eeccbdf20540..79556baed067 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.qs.tiles
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
import android.service.quicksettings.Tile
@@ -24,18 +26,26 @@ import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
+import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.flags.setFlagValue
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.plugins.ActivityStarter
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.QSComposeFragment
+import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
@@ -256,6 +266,41 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true)
}
+ @Test
+ @DisableFlags(QsDetailedView.FLAG_NAME)
+ fun click_withQsDetailedViewDisabled() {
+ underTest.click(null)
+ looper.processAllMessages()
+
+ verify(dialogManager, times(1)).create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ null,
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ value = [
+ QsDetailedView.FLAG_NAME,
+ FLAG_SCENE_CONTAINER,
+ KeyguardWmStateRefactor.FLAG_NAME,
+ NotificationThrottleHun.FLAG_NAME,
+ DualShade.FLAG_NAME]
+ )
+ fun click_withQsDetailedViewEnabled() {
+ underTest.click(null)
+ looper.processAllMessages()
+
+ verify(dialogManager, times(0)).create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ null,
+ )
+ }
+
companion object {
const val WIFI_SSID = "test ssid"
val ACTIVE_WIFI =
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/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index 04e094f25f5d..c8b3aba9b846 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -24,6 +24,7 @@ import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
@@ -87,9 +88,9 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() {
testScope.runTest {
val activeModes by collectLastValue(zenModeInteractor.activeModes)
+ zenModeRepository.activateMode(MANUAL_DND)
zenModeRepository.addModes(
listOf(
- TestModeBuilder.MANUAL_DND_ACTIVE,
TestModeBuilder().setName("Mode 1").setActive(true).build(),
TestModeBuilder().setName("Mode 2").setActive(true).build(),
)
@@ -111,7 +112,7 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() {
testScope.runTest {
val dndMode by collectLastValue(zenModeInteractor.dndMode)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ zenModeRepository.activateMode(MANUAL_DND)
assertThat(dndMode?.isActive).isTrue()
underTest.handleInput(
@@ -127,7 +128,6 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() {
testScope.runTest {
val dndMode by collectLastValue(zenModeInteractor.dndMode)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
assertThat(dndMode?.isActive).isFalse()
underTest.handleInput(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 48edded5df18..de54e75281aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -575,4 +575,50 @@ class SceneInteractorTest : SysuiTestCase() {
assertThat(currentScene).isNotEqualTo(disabledScene)
}
+
+ @Test
+ fun transitionAnimations() =
+ kosmos.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ assertThat(isVisible).isTrue()
+
+ underTest.setVisible(false, "test")
+ assertThat(isVisible).isFalse()
+
+ underTest.onTransitionAnimationStart()
+ // One animation is active, forced visible.
+ assertThat(isVisible).isTrue()
+
+ underTest.onTransitionAnimationEnd()
+ // No more active animations, not forced visible.
+ assertThat(isVisible).isFalse()
+
+ underTest.onTransitionAnimationStart()
+ // One animation is active, forced visible.
+ assertThat(isVisible).isTrue()
+
+ underTest.onTransitionAnimationCancelled()
+ // No more active animations, not forced visible.
+ assertThat(isVisible).isFalse()
+
+ underTest.setVisible(true, "test")
+ assertThat(isVisible).isTrue()
+
+ underTest.onTransitionAnimationStart()
+ underTest.onTransitionAnimationStart()
+ // Two animations are active, forced visible.
+ assertThat(isVisible).isTrue()
+
+ underTest.setVisible(false, "test")
+ // Two animations are active, forced visible.
+ assertThat(isVisible).isTrue()
+
+ underTest.onTransitionAnimationEnd()
+ // One animation is still active, forced visible.
+ assertThat(isVisible).isTrue()
+
+ underTest.onTransitionAnimationEnd()
+ // No more active animations, not forced visible.
+ assertThat(isVisible).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 06dd046564df..51f056aa18da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -36,6 +36,8 @@ import com.android.keyguard.AuthInteractionProperties
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.activityTransitionAnimator
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
@@ -141,6 +143,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.whenever
@SmallTest
@@ -169,6 +172,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
private val uiEventLoggerFake = kosmos.uiEventLoggerFake
private val msdlPlayer = kosmos.fakeMSDLPlayer
private val authInteractionProperties = AuthInteractionProperties()
+ private val mockActivityTransitionAnimator = mock<ActivityTransitionAnimator>()
private lateinit var underTest: SceneContainerStartable
@@ -177,6 +181,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
.thenReturn(true)
+ kosmos.activityTransitionAnimator = mockActivityTransitionAnimator
+
underTest = kosmos.sceneContainerStartable
}
@@ -2716,6 +2722,27 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentOverlays).isEmpty()
}
+ @Test
+ fun hydrateActivityTransitionAnimationState() =
+ kosmos.runTest {
+ underTest.start()
+
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+ assertThat(isVisible).isTrue()
+
+ sceneInteractor.setVisible(false, "reason")
+ assertThat(isVisible).isFalse()
+
+ val argumentCaptor = argumentCaptor<ActivityTransitionAnimator.Listener>()
+ verify(mockActivityTransitionAnimator).addListener(argumentCaptor.capture())
+
+ val listeners = argumentCaptor.allValues
+ listeners.forEach { it.onTransitionAnimationStart() }
+ assertThat(isVisible).isTrue()
+ listeners.forEach { it.onTransitionAnimationEnd() }
+ assertThat(isVisible).isFalse()
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
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/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 20474c842b51..deaf57999b21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -526,7 +526,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mLockscreenUserManager.mLastLockTime
.set(mSensitiveNotifPostTime - TimeUnit.DAYS.toMillis(1));
// Device is not currently locked
- when(mKeyguardManager.isDeviceLocked()).thenReturn(false);
+ mLockscreenUserManager.mLocked.set(false);
// Sensitive Content notifications are always redacted
assertEquals(REDACTION_TYPE_NONE,
@@ -540,7 +540,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mCurrentUser.id);
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+ mLockscreenUserManager.mLocked.set(true);
// Device was locked after this notification arrived
mLockscreenUserManager.mLastLockTime
.set(mSensitiveNotifPostTime + TimeUnit.DAYS.toMillis(1));
@@ -560,7 +560,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
// Device has been locked for 1 second before the notification came in, which is too short
mLockscreenUserManager.mLastLockTime
.set(mSensitiveNotifPostTime - TimeUnit.SECONDS.toMillis(1));
- when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+ mLockscreenUserManager.mLocked.set(true);
// Sensitive Content notifications are always redacted
assertEquals(REDACTION_TYPE_NONE,
@@ -577,7 +577,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
// Claim the device was last locked 1 day ago
mLockscreenUserManager.mLastLockTime
.set(mSensitiveNotifPostTime - TimeUnit.DAYS.toMillis(1));
- when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+ mLockscreenUserManager.mLocked.set(true);
// Sensitive Content notifications are always redacted
assertEquals(REDACTION_TYPE_NONE,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 2d7dc2e63650..0a0564994e69 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -43,6 +43,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
+import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import org.junit.Before
@@ -65,6 +66,7 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
+import java.util.Optional
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -82,6 +84,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Mock private lateinit var wallpaperController: WallpaperController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var appZoomOutOptional: Optional<AppZoomOut>
@Mock private lateinit var root: View
@Mock private lateinit var viewRootImpl: ViewRootImpl
@Mock private lateinit var windowToken: IBinder
@@ -128,6 +131,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
ResourcesSplitShadeStateController(),
windowRootViewBlurInteractor,
applicationScope,
+ appZoomOutOptional,
dumpManager,
configurationController,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 0061c4142e8b..75d000b63d62 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -22,7 +22,6 @@ import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
@@ -126,65 +125,8 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun chip_positiveStartTime_notifIconFlagOff_iconIsPhone() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 1000, notificationIcon = createStatusBarIconViewOrNull())
- )
-
- assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
- val icon =
- (((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
- .impl as Icon.Resource
- assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
- assertThat(icon.contentDescription).isNotNull()
- }
-
- @Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_positiveStartTime_notifIconFlagOn_iconIsNotifIcon() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- val notifIcon = createStatusBarIconViewOrNull()
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = notifIcon))
-
- assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
- val actualIcon =
- (((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.StatusBarView)
- .impl
- assertThat(actualIcon).isEqualTo(notifIcon)
- }
-
- @Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_positiveStartTime_notifIconFlagOn_cdFlagOn_iconIsNotifKeyIcon() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(
- startTimeMs = 1000,
- notificationIcon = createStatusBarIconViewOrNull(),
- notificationKey = "notifKey",
- )
- )
-
- assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
- }
-
- @Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIcon() =
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun chip_positiveStartTime_connectedDisplaysFlagOn_iconIsNotifIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -205,29 +147,8 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun chip_zeroStartTime_notifIconFlagOff_iconIsPhone() =
- testScope.runTest {
- val latest by collectLastValue(underTest.chip)
-
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 0, notificationIcon = createStatusBarIconViewOrNull())
- )
-
- assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
- val icon =
- (((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
- .impl as Icon.Resource
- assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
- assertThat(icon.contentDescription).isNotNull()
- }
-
- @Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_zeroStartTime_notifIconFlagOn_cdFlagOff_iconIsNotifIcon() =
+ fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -244,8 +165,8 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_zeroStartTime_notifIconFlagOn_cdFlagOn_iconIsNotifKeyIcon() =
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -262,7 +183,6 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOff_iconIsPhone() =
testScope.runTest {
@@ -281,7 +201,7 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_notifIconFlagOn_butNullNotifIcon_iconNotifKey() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index e561e3ea27d7..902db5e10589 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -501,7 +501,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
- fun chips_hasHeadsUpByUser_onlyShowsIcon() =
+ fun chips_hasHeadsUpBySystem_showsTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -523,7 +523,99 @@ class NotifChipsViewModelTest : SysuiTestCase() {
)
)
- // WHEN there's a HUN pinned by a user
+ // WHEN there's a HUN pinned by the system
+ kosmos.headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "notif",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ // THEN the chip keeps showing time
+ // (In real life the chip won't show at all, but that's handled in a different part of
+ // the system. What we know here is that the chip shouldn't shrink to icon only.)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ fun chips_hasHeadsUpByUser_forOtherNotif_showsTime() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+ val otherPromotedContentBuilder =
+ PromotedNotificationContentModel.Builder("other notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 654321L,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+ val icon = createStatusBarIconViewOrNull()
+ val otherIcon = createStatusBarIconViewOrNull()
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = icon,
+ promotedContent = promotedContentBuilder.build(),
+ ),
+ activeNotificationModel(
+ key = "other notif",
+ statusBarChipIcon = otherIcon,
+ promotedContent = otherPromotedContentBuilder.build(),
+ ),
+ )
+ )
+
+ // WHEN there's a HUN pinned for the "other notif" chip
+ kosmos.headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "other notif",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ // THEN the "notif" chip keeps showing time
+ val chip = latest!![0]
+ assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+ assertIsNotifChip(chip, icon, "notif")
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ fun chips_hasHeadsUpByUser_forThisNotif_onlyShowsIcon() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ // WHEN this notification is pinned by the user
kosmos.headsUpNotificationRepository.setNotifications(
UnconfinedFakeHeadsUpRowRepository(
key = "notif",
@@ -531,6 +623,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
)
)
+ // THEN the chip shrinks to icon only
assertThat(latest!![0])
.isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt
index c9c961791e89..49b95d92129c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt
@@ -45,7 +45,7 @@ import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
+import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelTest.kt
new file mode 100644
index 000000000000..72001758d01f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.layout.ui.viewmodel
+
+import android.content.res.Configuration
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.layout.statusBarContentInsetsProvider
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.fake
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class StatusBarContentInsetsViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val configuration = Configuration()
+
+ private val Kosmos.underTest by Kosmos.Fixture { statusBarContentInsetsViewModel }
+
+ @Test
+ fun contentArea_onMaxBoundsChanged_emitsNewValue() =
+ kosmos.runTest {
+ statusBarContentInsetsProvider.start()
+
+ val values by collectValues(underTest.contentArea)
+
+ // WHEN the content area changes
+ configurationController.fake.notifyLayoutDirectionChanged(isRtl = true)
+ configurationController.fake.notifyDensityOrFontScaleChanged()
+
+ // THEN the flow emits the new bounds
+ assertThat(values[0]).isNotEqualTo(values[1])
+ }
+
+ @Test
+ fun contentArea_onDensityOrFontScaleChanged_emitsLastBounds() =
+ kosmos.runTest {
+ configuration.densityDpi = 12
+ statusBarContentInsetsProvider.start()
+
+ val values by collectValues(underTest.contentArea)
+
+ // WHEN a change happens but it doesn't affect content area
+ configuration.densityDpi = 20
+ configurationController.onConfigurationChanged(configuration)
+ configurationController.fake.notifyDensityOrFontScaleChanged()
+
+ // THEN it still has the last bounds
+ assertThat(values).hasSize(1)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index a3ffd91b1728..609885d0214b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -458,7 +458,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun showPromotedNotification_hasNotifEntry_shownAsHUN() =
+ fun onPromotedNotificationChipTapped_hasNotifEntry_shownAsHUN() =
testScope.runTest {
whenever(notifCollection.getEntry(entry.key)).thenReturn(entry)
@@ -473,7 +473,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun showPromotedNotification_noNotifEntry_noHUN() =
+ fun onPromotedNotificationChipTapped_noNotifEntry_noHUN() =
testScope.runTest {
whenever(notifCollection.getEntry(entry.key)).thenReturn(null)
@@ -488,7 +488,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun showPromotedNotification_shownAsHUNEvenIfEntryShouldNot() =
+ fun onPromotedNotificationChipTapped_shownAsHUNEvenIfEntryShouldNot() =
testScope.runTest {
whenever(notifCollection.getEntry(entry.key)).thenReturn(entry)
@@ -511,7 +511,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun showPromotedNotification_atSameTimeAsOnAdded_promotedShownAsHUN() =
+ fun onPromotedNotificationChipTapped_atSameTimeAsOnAdded_promotedShownAsHUN() =
testScope.runTest {
// First, the promoted notification appears as not heads up
val promotedEntry = NotificationEntryBuilder().setPkg("promotedPackage").build()
@@ -548,6 +548,33 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun onPromotedNotificationChipTapped_chipTappedTwice_hunHiddenOnSecondTap() =
+ testScope.runTest {
+ whenever(notifCollection.getEntry(entry.key)).thenReturn(entry)
+
+ // WHEN chip tapped first
+ statusBarNotificationChipsInteractor.onPromotedNotificationChipTapped(entry.key)
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
+
+ // THEN HUN is shown
+ finishBind(entry)
+ verify(headsUpManager).showNotification(entry, isPinnedByUser = true)
+ addHUN(entry)
+
+ // WHEN chip is tapped again
+ statusBarNotificationChipsInteractor.onPromotedNotificationChipTapped(entry.key)
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
+
+ // THEN HUN is hidden
+ verify(headsUpManager).removeNotification(eq(entry.key), eq(false), any())
+ }
+
+ @Test
fun testTransferIsolatedChildAlert_withGroupAlertSummary() {
setShouldHeadsUp(groupSummary)
whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupSibling1))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 22ef408e266c..fae7d515d305 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.model.TopPinnedState
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -412,46 +413,53 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
@Test
fun statusBarHeadsUpState_pinnedBySystem() =
testScope.runTest {
- val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState)
+ val state by collectLastValue(underTest.statusBarHeadsUpState)
+ val status by collectLastValue(underTest.statusBarHeadsUpStatus)
headsUpRepository.setNotifications(
FakeHeadsUpRowRepository(key = "key 0", pinnedStatus = PinnedStatus.PinnedBySystem)
)
runCurrent()
- assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.PinnedBySystem)
+ assertThat(state).isEqualTo(TopPinnedState.Pinned("key 0", PinnedStatus.PinnedBySystem))
+ assertThat(status).isEqualTo(PinnedStatus.PinnedBySystem)
}
@Test
fun statusBarHeadsUpState_pinnedByUser() =
testScope.runTest {
- val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState)
+ val state by collectLastValue(underTest.statusBarHeadsUpState)
+ val status by collectLastValue(underTest.statusBarHeadsUpStatus)
headsUpRepository.setNotifications(
FakeHeadsUpRowRepository(key = "key 0", pinnedStatus = PinnedStatus.PinnedByUser)
)
runCurrent()
- assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.PinnedByUser)
+ assertThat(state).isEqualTo(TopPinnedState.Pinned("key 0", PinnedStatus.PinnedByUser))
+ assertThat(status).isEqualTo(PinnedStatus.PinnedByUser)
}
@Test
fun statusBarHeadsUpState_withoutPinnedNotifications_notPinned() =
testScope.runTest {
- val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState)
+ val state by collectLastValue(underTest.statusBarHeadsUpState)
+ val status by collectLastValue(underTest.statusBarHeadsUpStatus)
headsUpRepository.setNotifications(
FakeHeadsUpRowRepository(key = "key 0", PinnedStatus.NotPinned)
)
runCurrent()
- assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.NotPinned)
+ assertThat(state).isEqualTo(TopPinnedState.NothingPinned)
+ assertThat(status).isEqualTo(PinnedStatus.NotPinned)
}
@Test
fun statusBarHeadsUpState_whenShadeExpanded_false() =
testScope.runTest {
- val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState)
+ val state by collectLastValue(underTest.statusBarHeadsUpState)
+ val status by collectLastValue(underTest.statusBarHeadsUpStatus)
// WHEN a row is pinned
headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
@@ -463,13 +471,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
// should emit `false`.
kosmos.fakeShadeRepository.setLegacyShadeExpansion(1.0f)
- assertThat(statusBarHeadsUpState!!.isPinned).isFalse()
+ assertThat(state).isEqualTo(TopPinnedState.NothingPinned)
+ assertThat(status!!.isPinned).isFalse()
}
@Test
fun statusBarHeadsUpState_notificationsAreHidden_false() =
testScope.runTest {
- val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState)
+ val state by collectLastValue(underTest.statusBarHeadsUpState)
+ val status by collectLastValue(underTest.statusBarHeadsUpStatus)
// WHEN a row is pinned
headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
@@ -477,13 +487,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
// AND the notifications are hidden
keyguardViewStateRepository.areNotificationsFullyHidden.value = true
- assertThat(statusBarHeadsUpState!!.isPinned).isFalse()
+ assertThat(state).isEqualTo(TopPinnedState.NothingPinned)
+ assertThat(status!!.isPinned).isFalse()
}
@Test
fun statusBarHeadsUpState_onLockScreen_false() =
testScope.runTest {
- val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState)
+ val state by collectLastValue(underTest.statusBarHeadsUpState)
+ val status by collectLastValue(underTest.statusBarHeadsUpStatus)
// WHEN a row is pinned
headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
@@ -494,13 +506,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
testSetup = true,
)
- assertThat(statusBarHeadsUpState!!.isPinned).isFalse()
+ assertThat(state).isEqualTo(TopPinnedState.NothingPinned)
+ assertThat(status!!.isPinned).isFalse()
}
@Test
fun statusBarHeadsUpState_onByPassLockScreen_true() =
testScope.runTest {
- val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState)
+ val state by collectLastValue(underTest.statusBarHeadsUpState)
+ val status by collectLastValue(underTest.statusBarHeadsUpStatus)
// WHEN a row is pinned
headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
@@ -513,13 +527,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
// AND bypass is enabled
faceAuthRepository.isBypassEnabled.value = true
- assertThat(statusBarHeadsUpState!!.isPinned).isTrue()
+ assertThat(state).isInstanceOf(TopPinnedState.Pinned::class.java)
+ assertThat(status!!.isPinned).isTrue()
}
@Test
fun statusBarHeadsUpState_onByPassLockScreen_withoutNotifications_false() =
testScope.runTest {
- val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState)
+ val state by collectLastValue(underTest.statusBarHeadsUpState)
+ val status by collectLastValue(underTest.statusBarHeadsUpStatus)
// WHEN no pinned rows
// AND the lock screen is shown
@@ -530,7 +546,8 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
// AND bypass is enabled
faceAuthRepository.isBypassEnabled.value = true
- assertThat(statusBarHeadsUpState!!.isPinned).isFalse()
+ assertThat(state).isEqualTo(TopPinnedState.NothingPinned)
+ assertThat(status!!.isPinned).isFalse()
}
private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 72a91bc12f8d..14bbd38ece2c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -279,7 +279,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
notification);
RemoteViews headerRemoteViews;
if (lowPriority) {
- headerRemoteViews = builder.makeLowPriorityContentView(true, false);
+ headerRemoteViews = builder.makeLowPriorityContentView(true);
} else {
headerRemoteViews = builder.makeNotificationGroupHeader();
}
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/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 9eafcdbadfa5..e2330f448a1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserHandle;
@@ -431,9 +432,9 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
}
@Test
- public void onUdfpsConsecutivelyFailedThreeTimes_showPrimaryBouncer() {
- // GIVEN UDFPS is supported
- when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+ public void onOpticalUdfpsConsecutivelyFailedThreeTimes_showPrimaryBouncer() {
+ // GIVEN optical UDFPS is supported
+ when(mUpdateMonitor.isOpticalUdfpsSupported()).thenReturn(true);
// WHEN udfps fails once - then don't show the bouncer yet
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
@@ -451,6 +452,25 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
}
@Test
+ public void onUltrasonicUdfpsLockout_showPrimaryBouncer() {
+ // GIVEN ultrasonic UDFPS is supported
+ when(mUpdateMonitor.isOpticalUdfpsSupported()).thenReturn(false);
+
+ // WHEN udfps fails three times, don't show bouncer
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
+
+ // WHEN lockout is received
+ mBiometricUnlockController.onBiometricError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ "Lockout", BiometricSourceType.FINGERPRINT);
+
+ // THEN show bouncer
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
+ }
+
+ @Test
public void onFinishedGoingToSleep_authenticatesWhenPending() {
when(mUpdateMonitor.isGoingToSleep()).thenReturn(true);
mBiometricUnlockController.mWakefulnessObserver.onFinishedGoingToSleep();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index ffd349d744a8..43ad042ecf78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -53,7 +53,7 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
-import com.android.systemui.statusbar.layout.statusBarContentInsetsProvider
+import com.android.systemui.statusbar.layout.mockStatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
import com.android.systemui.statusbar.policy.BatteryController
@@ -153,7 +153,8 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
shadeViewStateProvider = TestShadeViewStateProvider()
Mockito.`when`(
- kosmos.statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+ kosmos.mockStatusBarContentInsetsProvider
+ .getStatusBarContentInsetsForCurrentRotation()
)
.thenReturn(Insets.of(0, 0, 0, 0))
@@ -162,7 +163,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(iconManager)
Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay)
- .thenReturn(kosmos.statusBarContentInsetsProvider)
+ .thenReturn(kosmos.mockStatusBarContentInsetsProvider)
allowTestableLooperAsMainThread()
looper.runWithLooper {
keyguardStatusBarView =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index cf512cdee800..b98409906f8d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -28,9 +28,7 @@ import android.view.View
import android.widget.LinearLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
-import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
import com.android.systemui.SysuiTestCase
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.dump.DumpManager
@@ -42,7 +40,6 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
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
@@ -76,9 +73,8 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
-@EnableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP)
@DisableFlags(StatusBarChipsModernization.FLAG_NAME)
-class OngoingCallControllerViaRepoTest : SysuiTestCase() {
+class OngoingCallControllerTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val clock = kosmos.fakeSystemClock
@@ -114,7 +110,6 @@ class OngoingCallControllerViaRepoTest : SysuiTestCase() {
testScope.backgroundScope,
context,
ongoingCallRepository,
- mock<CommonNotifCollection>(),
kosmos.activeNotificationsInteractor,
clock,
mockActivityStarter,
@@ -162,28 +157,7 @@ class OngoingCallControllerViaRepoTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun interactorHasOngoingCallNotif_notifIconFlagOff_repoHasNoNotifIcon() =
- testScope.runTest {
- val icon = mock<StatusBarIconView>()
- setNotifOnRepo(
- activeNotificationModel(
- key = "ongoingNotif",
- callType = CallType.Ongoing,
- uid = CALL_UID,
- statusBarChipIcon = icon,
- whenTime = 567,
- )
- )
-
- val repoState = ongoingCallRepository.ongoingCallState.value
- assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java)
- assertThat((repoState as OngoingCallModel.InCall).notificationIconView).isNull()
- }
-
- @Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun interactorHasOngoingCallNotif_notifIconFlagOn_repoHasNotifIcon() =
+ fun interactorHasOngoingCallNotif_repoHasNotifIcon() =
testScope.runTest {
val icon = mock<StatusBarIconView>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
deleted file mode 100644
index cd3539d6b9a5..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ /dev/null
@@ -1,694 +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.phone.ongoingcall
-
-import android.app.ActivityManager
-import android.app.IActivityManager
-import android.app.IUidObserver
-import android.app.Notification
-import android.app.PendingIntent
-import android.app.Person
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.service.notification.NotificationListenerService.REASON_USER_STOPPED
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.LinearLayout
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
-import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
-import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
-import com.android.systemui.statusbar.window.StatusBarWindowController
-import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.ArgumentMatchers.nullable
-import org.mockito.Mock
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.whenever
-
-private const val CALL_UID = 900
-
-// A process state that represents the process being visible to the user.
-private const val PROC_STATE_VISIBLE = ActivityManager.PROCESS_STATE_TOP
-
-// A process state that represents the process being invisible to the user.
-private const val PROC_STATE_INVISIBLE = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-@OptIn(ExperimentalCoroutinesApi::class)
-@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP, StatusBarChipsModernization.FLAG_NAME)
-class OngoingCallControllerViaListenerTest : SysuiTestCase() {
- private val kosmos = Kosmos()
-
- private val clock = FakeSystemClock()
- private val mainExecutor = FakeExecutor(clock)
- private val testScope = TestScope()
- private val statusBarModeRepository = FakeStatusBarModeRepository()
- private val ongoingCallRepository = kosmos.ongoingCallRepository
-
- private lateinit var controller: OngoingCallController
- private lateinit var notifCollectionListener: NotifCollectionListener
-
- @Mock
- private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
- @Mock private lateinit var mockOngoingCallListener: OngoingCallListener
- @Mock private lateinit var mockActivityStarter: ActivityStarter
- @Mock private lateinit var mockIActivityManager: IActivityManager
- @Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController
- @Mock private lateinit var mockStatusBarWindowControllerStore: StatusBarWindowControllerStore
-
- private lateinit var chipView: View
-
- @Before
- fun setUp() {
- allowTestableLooperAsMainThread()
- TestableLooper.get(this).runWithLooper {
- chipView =
- LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip_primary, null)
- }
-
- MockitoAnnotations.initMocks(this)
- val notificationCollection = mock(CommonNotifCollection::class.java)
- whenever(mockStatusBarWindowControllerStore.defaultDisplay)
- .thenReturn(mockStatusBarWindowController)
-
- controller =
- OngoingCallController(
- testScope.backgroundScope,
- context,
- ongoingCallRepository,
- notificationCollection,
- kosmos.activeNotificationsInteractor,
- clock,
- mockActivityStarter,
- mainExecutor,
- mockIActivityManager,
- DumpManager(),
- mockStatusBarWindowControllerStore,
- mockSwipeStatusBarAwayGestureHandler,
- statusBarModeRepository,
- logcatLogBuffer("OngoingCallControllerViaListenerTest"),
- )
- controller.start()
- controller.addCallback(mockOngoingCallListener)
- controller.setChipView(chipView)
- onTeardown { controller.tearDownChipView() }
-
- val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
- verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
- notifCollectionListener = collectionListenerCaptor.value!!
-
- `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_INVISIBLE)
- }
-
- @Test
- fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
- val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
- notification.modifyNotification(context).setWhen(567)
- notifCollectionListener.onEntryUpdated(notification.build())
-
- verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
- val repoState = ongoingCallRepository.ongoingCallState.value
- assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java)
- assertThat((repoState as OngoingCallModel.InCall).startTimeMs).isEqualTo(567)
- }
-
- @Test
- fun onEntryUpdated_isOngoingCallNotif_windowControllerUpdated() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(true)
- }
-
- @Test
- fun onEntryUpdated_notOngoingCallNotif_listenerAndRepoNotNotified() {
- notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
-
- verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
- assertThat(ongoingCallRepository.ongoingCallState.value)
- .isInstanceOf(OngoingCallModel.NoCall::class.java)
- }
-
- @Test
- fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_listenerNotifiedTwice() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
-
- verify(mockOngoingCallListener, times(2)).onOngoingCallStateChanged(anyBoolean())
- }
-
- @Test
- fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_repoUpdated() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- assertThat(ongoingCallRepository.ongoingCallState.value)
- .isInstanceOf(OngoingCallModel.InCall::class.java)
-
- notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
-
- assertThat(ongoingCallRepository.ongoingCallState.value)
- .isInstanceOf(OngoingCallModel.NoCall::class.java)
- }
-
- /** Regression test for b/191472854. */
- @Test
- fun onEntryUpdated_notifHasNullContentIntent_noCrash() {
- notifCollectionListener.onEntryUpdated(
- createCallNotifEntry(ongoingCallStyle, nullContentIntent = true)
- )
- }
-
- /** Regression test for b/192379214. */
- @Test
- @DisableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME, FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun onEntryUpdated_notificationWhenIsZero_timeHidden() {
- val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
- notification.modifyNotification(context).setWhen(0)
-
- notifCollectionListener.onEntryUpdated(notification.build())
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- )
-
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
- .isEqualTo(0)
- }
-
- @Test
- @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun onEntryUpdated_notificationWhenIsZero_timeShown() {
- val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
- notification.modifyNotification(context).setWhen(0)
-
- notifCollectionListener.onEntryUpdated(notification.build())
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- )
-
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
- .isGreaterThan(0)
- }
-
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun onEntryUpdated_notificationWhenIsValid_timeShown() {
- val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
- notification.modifyNotification(context).setWhen(clock.currentTimeMillis())
-
- notifCollectionListener.onEntryUpdated(notification.build())
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- )
-
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
- .isGreaterThan(0)
- }
-
- /** Regression test for b/194731244. */
- @Test
- fun onEntryUpdated_calledManyTimes_uidObserverOnlyRegisteredOnce() {
- for (i in 0 until 4) {
- // Re-create the notification each time so that it's considered a different object and
- // will re-trigger the whole flow.
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- }
-
- verify(mockIActivityManager, times(1)).registerUidObserver(any(), any(), any(), any())
- }
-
- /** Regression test for b/216248574. */
- @Test
- fun entryUpdated_getUidProcessStateThrowsException_noCrash() {
- `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenThrow(SecurityException())
-
- // No assert required, just check no crash
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- }
-
- /** Regression test for b/216248574. */
- @Test
- fun entryUpdated_registerUidObserverThrowsException_noCrash() {
- `when`(
- mockIActivityManager.registerUidObserver(
- any(),
- any(),
- any(),
- nullable(String::class.java),
- )
- )
- .thenThrow(SecurityException())
-
- // No assert required, just check no crash
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- }
-
- /** Regression test for b/216248574. */
- @Test
- fun entryUpdated_packageNameProvidedToActivityManager() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- val packageNameCaptor = ArgumentCaptor.forClass(String::class.java)
- verify(mockIActivityManager)
- .registerUidObserver(any(), any(), any(), packageNameCaptor.capture())
- assertThat(packageNameCaptor.value).isNotNull()
- }
-
- /**
- * If a call notification is never added before #onEntryRemoved is called, then the listener
- * should never be notified.
- */
- @Test
- fun onEntryRemoved_callNotifNeverAddedBeforehand_listenerNotNotified() {
- notifCollectionListener.onEntryRemoved(createOngoingCallNotifEntry(), REASON_USER_STOPPED)
-
- verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
- }
-
- @Test
- fun onEntryRemoved_callNotifAddedThenRemoved_listenerNotified() {
- val ongoingCallNotifEntry = createOngoingCallNotifEntry()
- notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
- reset(mockOngoingCallListener)
-
- notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
-
- verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
- }
-
- @Test
- fun onEntryRemoved_callNotifAddedThenRemoved_repoUpdated() {
- val ongoingCallNotifEntry = createOngoingCallNotifEntry()
- notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
- assertThat(ongoingCallRepository.ongoingCallState.value)
- .isInstanceOf(OngoingCallModel.InCall::class.java)
-
- notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
-
- assertThat(ongoingCallRepository.ongoingCallState.value)
- .isInstanceOf(OngoingCallModel.NoCall::class.java)
- }
-
- @Test
- fun onEntryUpdated_callNotifAddedThenRemoved_windowControllerUpdated() {
- val ongoingCallNotifEntry = createOngoingCallNotifEntry()
- notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
-
- notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
-
- verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false)
- }
-
- /** Regression test for b/188491504. */
- @Test
- fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() {
- val ongoingCallNotifEntry = createOngoingCallNotifEntry()
- notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
- reset(mockOngoingCallListener)
-
- // Create another notification based on the ongoing call one, but remove the features that
- // made it a call notification.
- val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry)
- removedEntryBuilder.modifyNotification(context).style = null
-
- notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED)
-
- verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
- }
-
- /** Regression test for b/188491504. */
- @Test
- fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_repoUpdated() {
- val ongoingCallNotifEntry = createOngoingCallNotifEntry()
- notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
-
- // Create another notification based on the ongoing call one, but remove the features that
- // made it a call notification.
- val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry)
- removedEntryBuilder.modifyNotification(context).style = null
-
- notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED)
-
- assertThat(ongoingCallRepository.ongoingCallState.value)
- .isInstanceOf(OngoingCallModel.NoCall::class.java)
- }
-
- @Test
- fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_listenerNotNotified() {
- notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
- reset(mockOngoingCallListener)
-
- notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
-
- verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
- }
-
- @Test
- fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_repoNotUpdated() {
- notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
-
- notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
-
- assertThat(ongoingCallRepository.ongoingCallState.value)
- .isInstanceOf(OngoingCallModel.InCall::class.java)
- }
-
- @Test
- fun hasOngoingCall_noOngoingCallNotifSent_returnsFalse() {
- assertThat(controller.hasOngoingCall()).isFalse()
- }
-
- @Test
- fun hasOngoingCall_unrelatedNotifSent_returnsFalse() {
- notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
-
- assertThat(controller.hasOngoingCall()).isFalse()
- }
-
- @Test
- fun hasOngoingCall_screeningCallNotifSent_returnsFalse() {
- notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
-
- assertThat(controller.hasOngoingCall()).isFalse()
- }
-
- @Test
- fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() {
- `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_INVISIBLE)
-
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- assertThat(controller.hasOngoingCall()).isTrue()
- }
-
- @Test
- fun hasOngoingCall_ongoingCallNotifSentButCallAppVisible_returnsFalse() {
- `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_VISIBLE)
-
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- assertThat(controller.hasOngoingCall()).isFalse()
- }
-
- @Test
- fun hasOngoingCall_ongoingCallNotifSentButInvalidChipView_returnsFalse() {
- val invalidChipView = LinearLayout(context)
- controller.setChipView(invalidChipView)
-
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- assertThat(controller.hasOngoingCall()).isFalse()
- }
-
- @Test
- fun hasOngoingCall_ongoingCallNotifSentThenRemoved_returnsFalse() {
- val ongoingCallNotifEntry = createOngoingCallNotifEntry()
-
- notifCollectionListener.onEntryUpdated(ongoingCallNotifEntry)
- notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
-
- assertThat(controller.hasOngoingCall()).isFalse()
- }
-
- @Test
- fun hasOngoingCall_ongoingCallNotifSentThenScreeningCallNotifSent_returnsFalse() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
-
- assertThat(controller.hasOngoingCall()).isFalse()
- }
-
- @Test
- fun hasOngoingCall_ongoingCallNotifSentThenUnrelatedNotifSent_returnsTrue() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
-
- assertThat(controller.hasOngoingCall()).isTrue()
- }
-
- /**
- * This test fakes a theme change during an ongoing call.
- *
- * When a theme change happens, [CollapsedStatusBarFragment] and its views get re-created, so
- * [OngoingCallController.setChipView] gets called with a new view. If there's an active ongoing
- * call when the theme changes, the new view needs to be updated with the call information.
- */
- @Test
- fun setChipView_whenHasOngoingCallIsTrue_listenerNotifiedWithNewView() {
- // Start an ongoing call.
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- reset(mockOngoingCallListener)
-
- lateinit var newChipView: View
- TestableLooper.get(this).runWithLooper {
- newChipView =
- LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip_primary, null)
- }
-
- // Change the chip view associated with the controller.
- controller.setChipView(newChipView)
-
- verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
- }
-
- @Test
- fun callProcessChangesToVisible_listenerNotified() {
- // Start the call while the process is invisible.
- `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_INVISIBLE)
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- reset(mockOngoingCallListener)
-
- val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
- verify(mockIActivityManager)
- .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java))
- val uidObserver = captor.value
-
- // Update the process to visible.
- uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_VISIBLE, 0, 0)
- mainExecutor.advanceClockToLast()
- mainExecutor.runAllReady()
-
- verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
- }
-
- @Test
- fun callProcessChangesToInvisible_listenerNotified() {
- // Start the call while the process is visible.
- `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
- .thenReturn(PROC_STATE_VISIBLE)
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- reset(mockOngoingCallListener)
-
- val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
- verify(mockIActivityManager)
- .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java))
- val uidObserver = captor.value
-
- // Update the process to invisible.
- uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_INVISIBLE, 0, 0)
- mainExecutor.advanceClockToLast()
- mainExecutor.runAllReady()
-
- verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
- }
-
- /** Regression test for b/212467440. */
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun chipClicked_activityStarterTriggeredWithUnmodifiedIntent() {
- val notifEntry = createOngoingCallNotifEntry()
- val pendingIntent = notifEntry.sbn.notification.contentIntent
- notifCollectionListener.onEntryUpdated(notifEntry)
-
- chipView.performClick()
-
- // Ensure that the sysui didn't modify the notification's intent -- see b/212467440.
- verify(mockActivityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
- }
-
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun callNotificationAdded_chipIsClickable() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- assertThat(chipView.hasOnClickListeners()).isTrue()
- }
-
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun callNotificationAdded_newChipsEnabled_chipNotClickable() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- assertThat(chipView.hasOnClickListeners()).isFalse()
- }
-
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun fullscreenIsTrue_chipStillClickable() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
- testScope.runCurrent()
-
- assertThat(chipView.hasOnClickListeners()).isTrue()
- }
-
- // Swipe gesture tests
-
- @Test
- fun callStartedInImmersiveMode_swipeGestureCallbackAdded() {
- statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
- testScope.runCurrent()
-
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- verify(mockSwipeStatusBarAwayGestureHandler)
- .addOnGestureDetectedCallback(anyString(), any())
- }
-
- @Test
- fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() {
- statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
- testScope.runCurrent()
-
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- verify(mockSwipeStatusBarAwayGestureHandler, never())
- .addOnGestureDetectedCallback(anyString(), any())
- }
-
- @Test
- fun transitionToImmersiveMode_swipeGestureCallbackAdded() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
- testScope.runCurrent()
-
- verify(mockSwipeStatusBarAwayGestureHandler)
- .addOnGestureDetectedCallback(anyString(), any())
- }
-
- @Test
- fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() {
- statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- reset(mockSwipeStatusBarAwayGestureHandler)
-
- statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
- testScope.runCurrent()
-
- verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString())
- }
-
- @Test
- fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() {
- statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
- testScope.runCurrent()
- val ongoingCallNotifEntry = createOngoingCallNotifEntry()
- notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
- reset(mockSwipeStatusBarAwayGestureHandler)
-
- notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
-
- verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString())
- }
-
- // TODO(b/195839150): Add test
- // swipeGesturedTriggeredPreviously_entersImmersiveModeAgain_callbackNotAdded(). That's
- // difficult to add now because we have no way to trigger [SwipeStatusBarAwayGestureHandler]'s
- // callbacks in test.
-
- // END swipe gesture tests
-
- private fun createOngoingCallNotifEntry() = createCallNotifEntry(ongoingCallStyle)
-
- private fun createScreeningCallNotifEntry() = createCallNotifEntry(screeningCallStyle)
-
- private fun createCallNotifEntry(
- callStyle: Notification.CallStyle,
- nullContentIntent: Boolean = false,
- ): NotificationEntry {
- val notificationEntryBuilder = NotificationEntryBuilder()
- notificationEntryBuilder.modifyNotification(context).style = callStyle
- notificationEntryBuilder.setUid(CALL_UID)
-
- if (nullContentIntent) {
- notificationEntryBuilder.modifyNotification(context).setContentIntent(null)
- } else {
- val contentIntent = mock(PendingIntent::class.java)
- notificationEntryBuilder.modifyNotification(context).setContentIntent(contentIntent)
- }
-
- return notificationEntryBuilder.build()
- }
-
- private fun createNotCallNotifEntry() = NotificationEntryBuilder().build()
-}
-
-private val person = Person.Builder().setName("name").build()
-private val hangUpIntent = mock(PendingIntent::class.java)
-
-private val ongoingCallStyle = Notification.CallStyle.forOngoingCall(person, hangUpIntent)
-private val screeningCallStyle =
- Notification.CallStyle.forScreeningCall(
- person,
- hangUpIntent,
- /* answerIntent= */ mock(PendingIntent::class.java),
- )
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..6feada1c9769 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,14 @@ class FakeHomeStatusBarViewModel(
override val iconBlockList: MutableStateFlow<List<String>> = MutableStateFlow(listOf())
- override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut
+ override val contentArea = MutableStateFlow(Rect(0, 0, 1, 1))
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..e95bc3378423 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,13 +22,17 @@ 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
+import android.view.Display.DEFAULT_DISPLAY
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.fake
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -59,7 +63,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
@@ -85,6 +88,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Before
import org.junit.Test
@@ -104,6 +108,9 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
setUpPackageManagerForMediaProjection(kosmos)
}
+ @Before
+ fun addDisplays() = runBlocking { kosmos.displayRepository.fake.addDisplay(DEFAULT_DISPLAY) }
+
@Test
fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
kosmos.runTest {
@@ -363,7 +370,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 +384,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 +398,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 +412,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 +422,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 +1012,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 +1026,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 +1040,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/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index c6bae197ad76..7802b921eae0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.settingslib.notification.data.repository.updateNotificationPolicy
import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -204,7 +205,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun shouldAskForZenDuration_changesWithSetting() =
testScope.runTest {
- val manualDnd = TestModeBuilder.MANUAL_DND_ACTIVE
+ val manualDnd = TestModeBuilder().makeManualDnd().setActive(true).build()
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
runCurrent()
@@ -233,29 +234,27 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun activateMode_usesCorrectDuration() =
testScope.runTest {
- val manualDnd = TestModeBuilder.MANUAL_DND_ACTIVE
- zenModeRepository.addModes(listOf(manualDnd))
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
runCurrent()
- underTest.activateMode(manualDnd)
- assertThat(zenModeRepository.getModeActiveDuration(manualDnd.id)).isNull()
+ underTest.activateMode(MANUAL_DND)
+ assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id)).isNull()
- zenModeRepository.deactivateMode(manualDnd.id)
+ zenModeRepository.deactivateMode(MANUAL_DND)
settingsRepository.setInt(ZEN_DURATION, 60)
runCurrent()
- underTest.activateMode(manualDnd)
- assertThat(zenModeRepository.getModeActiveDuration(manualDnd.id))
+ underTest.activateMode(MANUAL_DND)
+ assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id))
.isEqualTo(Duration.ofMinutes(60))
}
@Test
fun deactivateAllModes_updatesCorrectModes() =
testScope.runTest {
+ zenModeRepository.activateMode(MANUAL_DND)
zenModeRepository.addModes(
listOf(
- TestModeBuilder.MANUAL_DND_ACTIVE,
TestModeBuilder().setName("Inactive").setActive(false).build(),
TestModeBuilder().setName("Active").setActive(true).build(),
)
@@ -389,12 +388,9 @@ class ZenModeInteractorTest : SysuiTestCase() {
testScope.runTest {
val dndMode by collectLastValue(underTest.dndMode)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
- runCurrent()
-
assertThat(dndMode!!.isActive).isFalse()
- zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+ zenModeRepository.activateMode(MANUAL_DND)
runCurrent()
assertThat(dndMode!!.isActive).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 07d088bbb3d6..856de8ee1c80 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -28,6 +28,7 @@ import android.service.notification.ZenModeConfig.ScheduleInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -111,7 +112,6 @@ class ModesDialogViewModelTest : SysuiTestCase() {
.setName("Disabled by other")
.setEnabled(false, /* byUser= */ false)
.build(),
- TestModeBuilder.MANUAL_DND_ACTIVE,
TestModeBuilder()
.setName("Enabled")
.setEnabled(true)
@@ -128,14 +128,14 @@ class ModesDialogViewModelTest : SysuiTestCase() {
assertThat(tiles).hasSize(3)
with(tiles?.elementAt(0)!!) {
- assertThat(this.text).isEqualTo("Disabled by other")
- assertThat(this.subtext).isEqualTo("Not set")
+ assertThat(this.text).isEqualTo("Do Not Disturb")
+ assertThat(this.subtext).isEqualTo("Off")
assertThat(this.enabled).isEqualTo(false)
}
with(tiles?.elementAt(1)!!) {
- assertThat(this.text).isEqualTo("Do Not Disturb")
- assertThat(this.subtext).isEqualTo("On")
- assertThat(this.enabled).isEqualTo(true)
+ assertThat(this.text).isEqualTo("Disabled by other")
+ assertThat(this.subtext).isEqualTo("Not set")
+ assertThat(this.enabled).isEqualTo(false)
}
with(tiles?.elementAt(2)!!) {
assertThat(this.text).isEqualTo("Enabled")
@@ -176,18 +176,24 @@ class ModesDialogViewModelTest : SysuiTestCase() {
)
runCurrent()
- assertThat(tiles).hasSize(3)
+ // Manual DND is included by default
+ assertThat(tiles).hasSize(4)
with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Do Not Disturb")
+ assertThat(this.subtext).isEqualTo("Off")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Active without manual")
assertThat(this.subtext).isEqualTo("On")
assertThat(this.enabled).isEqualTo(true)
}
- with(tiles?.elementAt(1)!!) {
+ with(tiles?.elementAt(2)!!) {
assertThat(this.text).isEqualTo("Active with manual")
assertThat(this.subtext).isEqualTo("On • trigger description")
assertThat(this.enabled).isEqualTo(true)
}
- with(tiles?.elementAt(2)!!) {
+ with(tiles?.elementAt(3)!!) {
assertThat(this.text).isEqualTo("Inactive with manual")
assertThat(this.subtext).isEqualTo("Off")
assertThat(this.enabled).isEqualTo(false)
@@ -226,10 +232,11 @@ class ModesDialogViewModelTest : SysuiTestCase() {
)
runCurrent()
- assertThat(tiles).hasSize(3)
+ // Manual DND is included by default
+ assertThat(tiles).hasSize(4)
// Check that tile is initially present
- with(tiles?.elementAt(0)!!) {
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Active without manual")
assertThat(this.subtext).isEqualTo("On")
assertThat(this.enabled).isEqualTo(true)
@@ -239,8 +246,8 @@ class ModesDialogViewModelTest : SysuiTestCase() {
runCurrent()
}
// Check that tile is still present at the same location, but turned off
- assertThat(tiles).hasSize(3)
- with(tiles?.elementAt(0)!!) {
+ assertThat(tiles).hasSize(4)
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Active without manual")
assertThat(this.subtext).isEqualTo("Manage in settings")
assertThat(this.enabled).isEqualTo(false)
@@ -252,9 +259,9 @@ class ModesDialogViewModelTest : SysuiTestCase() {
runCurrent()
// Check that tile is now gone
- assertThat(tiles2).hasSize(2)
- assertThat(tiles2?.elementAt(0)!!.text).isEqualTo("Active with manual")
- assertThat(tiles2?.elementAt(1)!!.text).isEqualTo("Inactive with manual")
+ assertThat(tiles2).hasSize(3)
+ assertThat(tiles2?.elementAt(1)!!.text).isEqualTo("Active with manual")
+ assertThat(tiles2?.elementAt(2)!!.text).isEqualTo("Inactive with manual")
}
@Test
@@ -287,22 +294,23 @@ class ModesDialogViewModelTest : SysuiTestCase() {
)
runCurrent()
- assertThat(tiles).hasSize(3)
+ // Manual DND is included by default
+ assertThat(tiles).hasSize(4)
repository.removeMode("A")
runCurrent()
- assertThat(tiles).hasSize(2)
+ assertThat(tiles).hasSize(3)
repository.removeMode("B")
runCurrent()
- assertThat(tiles).hasSize(1)
+ assertThat(tiles).hasSize(2)
repository.removeMode("C")
runCurrent()
- assertThat(tiles).hasSize(0)
+ assertThat(tiles).hasSize(1)
}
@Test
@@ -353,14 +361,15 @@ class ModesDialogViewModelTest : SysuiTestCase() {
)
runCurrent()
- assertThat(tiles!!).hasSize(7)
- assertThat(tiles!![0].subtext).isEqualTo("When the going gets tough")
- assertThat(tiles!![1].subtext).isEqualTo("On • When in Rome")
- assertThat(tiles!![2].subtext).isEqualTo("Not set")
- assertThat(tiles!![3].subtext).isEqualTo("Off")
- assertThat(tiles!![4].subtext).isEqualTo("On")
- assertThat(tiles!![5].subtext).isEqualTo("Not set")
- assertThat(tiles!![6].subtext).isEqualTo(timeScheduleMode.triggerDescription)
+ // Manual DND is included by default
+ assertThat(tiles!!).hasSize(8)
+ assertThat(tiles!![1].subtext).isEqualTo("When the going gets tough")
+ assertThat(tiles!![2].subtext).isEqualTo("On • When in Rome")
+ assertThat(tiles!![3].subtext).isEqualTo("Not set")
+ assertThat(tiles!![4].subtext).isEqualTo("Off")
+ assertThat(tiles!![5].subtext).isEqualTo("On")
+ assertThat(tiles!![6].subtext).isEqualTo("Not set")
+ assertThat(tiles!![7].subtext).isEqualTo(timeScheduleMode.triggerDescription)
}
@Test
@@ -411,32 +420,33 @@ class ModesDialogViewModelTest : SysuiTestCase() {
)
runCurrent()
- assertThat(tiles!!).hasSize(7)
- with(tiles?.elementAt(0)!!) {
+ // Manual DND is included by default
+ assertThat(tiles!!).hasSize(8)
+ with(tiles?.elementAt(1)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
assertThat(this.subtextDescription).isEqualTo("When the going gets tough")
}
- with(tiles?.elementAt(1)!!) {
+ with(tiles?.elementAt(2)!!) {
assertThat(this.stateDescription).isEqualTo("On")
assertThat(this.subtextDescription).isEqualTo("When in Rome")
}
- with(tiles?.elementAt(2)!!) {
+ with(tiles?.elementAt(3)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
assertThat(this.subtextDescription).isEqualTo("Not set")
}
- with(tiles?.elementAt(3)!!) {
+ with(tiles?.elementAt(4)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
assertThat(this.subtextDescription).isEmpty()
}
- with(tiles?.elementAt(4)!!) {
+ with(tiles?.elementAt(5)!!) {
assertThat(this.stateDescription).isEqualTo("On")
assertThat(this.subtextDescription).isEmpty()
}
- with(tiles?.elementAt(5)!!) {
+ with(tiles?.elementAt(6)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
assertThat(this.subtextDescription).isEqualTo("Not set")
}
- with(tiles?.elementAt(6)!!) {
+ with(tiles?.elementAt(7)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
assertThat(this.subtextDescription)
.isEqualTo(
@@ -456,31 +466,30 @@ class ModesDialogViewModelTest : SysuiTestCase() {
val tiles by collectLastValue(underTest.tiles)
val modeId = "id"
- repository.addModes(
- listOf(
- TestModeBuilder()
- .setId(modeId)
- .setName("Test")
- .setManualInvocationAllowed(true)
- .build()
- )
+ repository.addMode(
+ TestModeBuilder()
+ .setId(modeId)
+ .setName("Test")
+ .setManualInvocationAllowed(true)
+ .build()
)
runCurrent()
- assertThat(tiles).hasSize(1)
- assertThat(tiles?.elementAt(0)?.enabled).isFalse()
+ // Manual DND is included by default
+ assertThat(tiles).hasSize(2)
+ assertThat(tiles?.elementAt(1)?.enabled).isFalse()
// Trigger onClick
- tiles?.first()?.onClick?.let { it() }
+ tiles?.elementAt(1)?.onClick?.let { it() }
runCurrent()
- assertThat(tiles?.first()?.enabled).isTrue()
+ assertThat(tiles?.elementAt(1)?.enabled).isTrue()
// Trigger onClick
- tiles?.first()?.onClick?.let { it() }
+ tiles?.elementAt(1)?.onClick?.let { it() }
runCurrent()
- assertThat(tiles?.first()?.enabled).isFalse()
+ assertThat(tiles?.elementAt(1)?.enabled).isFalse()
}
@Test
@@ -489,25 +498,24 @@ class ModesDialogViewModelTest : SysuiTestCase() {
val job = Job()
val tiles by collectLastValue(underTest.tiles, context = job)
- repository.addModes(
- listOf(
- TestModeBuilder()
- .setName("Active without manual")
- .setActive(true)
- .setManualInvocationAllowed(false)
- .build()
- )
+ repository.addMode(
+ TestModeBuilder()
+ .setName("Active without manual")
+ .setActive(true)
+ .setManualInvocationAllowed(false)
+ .build()
)
runCurrent()
- assertThat(tiles).hasSize(1)
+ // Manual DND is included by default
+ assertThat(tiles).hasSize(2)
// Click tile to toggle it off
- tiles?.elementAt(0)!!.onClick()
+ tiles?.elementAt(1)!!.onClick()
runCurrent()
- assertThat(tiles).hasSize(1)
- with(tiles?.elementAt(0)!!) {
+ assertThat(tiles).hasSize(2)
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Active without manual")
assertThat(this.subtext).isEqualTo("Manage in settings")
assertThat(this.enabled).isEqualTo(false)
@@ -518,7 +526,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
}
// Check that nothing happened
- with(tiles?.elementAt(0)!!) {
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Active without manual")
assertThat(this.subtext).isEqualTo("Manage in settings")
assertThat(this.enabled).isEqualTo(false)
@@ -530,19 +538,18 @@ class ModesDialogViewModelTest : SysuiTestCase() {
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
- repository.addModes(
- listOf(
- TestModeBuilder()
- .setId("ID")
- .setName("Disabled by other")
- .setEnabled(false, /* byUser= */ false)
- .build()
- )
+ repository.addMode(
+ TestModeBuilder()
+ .setId("ID")
+ .setName("Disabled by other")
+ .setEnabled(false, /* byUser= */ false)
+ .build()
)
runCurrent()
- assertThat(tiles).hasSize(1)
- with(tiles?.elementAt(0)!!) {
+ // Manual DND is included by default
+ assertThat(tiles).hasSize(2)
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
@@ -561,7 +568,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
.isEqualTo("ID")
// Check that nothing happened to the tile
- with(tiles?.elementAt(0)!!) {
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Disabled by other")
assertThat(this.subtext).isEqualTo("Not set")
assertThat(this.enabled).isEqualTo(false)
@@ -593,10 +600,11 @@ class ModesDialogViewModelTest : SysuiTestCase() {
)
runCurrent()
- assertThat(tiles).hasSize(2)
+ // Manual DND is included by default
+ assertThat(tiles).hasSize(3)
// Trigger onLongClick for A
- tiles?.first()?.onLongClick?.let { it() }
+ tiles?.elementAt(1)?.onLongClick?.let { it() }
runCurrent()
// Check that it launched the correct intent
@@ -625,9 +633,9 @@ class ModesDialogViewModelTest : SysuiTestCase() {
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
+ repository.activateMode(MANUAL_DND)
repository.addModes(
listOf(
- TestModeBuilder.MANUAL_DND_ACTIVE,
TestModeBuilder()
.setId("id1")
.setName("Inactive Mode One")
@@ -644,6 +652,7 @@ class ModesDialogViewModelTest : SysuiTestCase() {
)
runCurrent()
+ // Manual DND is included by default
assertThat(tiles).hasSize(3)
// Trigger onClick for each tile in sequence
@@ -672,19 +681,17 @@ class ModesDialogViewModelTest : SysuiTestCase() {
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
- repository.addModes(
- listOf(
- TestModeBuilder.MANUAL_DND_ACTIVE,
- TestModeBuilder()
- .setId("id1")
- .setName("Inactive Mode One")
- .setActive(false)
- .setManualInvocationAllowed(true)
- .build(),
- )
+ repository.addMode(
+ TestModeBuilder()
+ .setId("id1")
+ .setName("Inactive Mode One")
+ .setActive(false)
+ .setManualInvocationAllowed(true)
+ .build()
)
runCurrent()
+ // Manual DND is included by default
assertThat(tiles).hasSize(2)
val modeCaptor = argumentCaptor<ZenMode>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
index 61c719319de8..824955de83e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.policy.statusBarConfigurationController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -41,13 +42,18 @@ class StatusBarWindowControllerImplTest : SysuiTestCase() {
private val kosmos =
testKosmos().also { it.statusBarWindowViewInflater = it.fakeStatusBarWindowViewInflater }
- private val underTest = kosmos.statusBarWindowControllerImpl
+ private lateinit var underTest: StatusBarWindowControllerImpl
private val fakeExecutor = kosmos.fakeExecutor
private val fakeWindowManager = kosmos.fakeWindowManager
private val mockFragmentService = kosmos.fragmentService
private val fakeStatusBarWindowViewInflater = kosmos.fakeStatusBarWindowViewInflater
private val statusBarConfigurationController = kosmos.statusBarConfigurationController
+ @Before
+ fun setUp() {
+ underTest = kosmos.statusBarWindowControllerImpl
+ }
+
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun attach_connectedDisplaysFlagEnabled_setsConfigControllerOnWindowView() {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt
index d84d89087349..812a964bf8bc 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt
@@ -30,10 +30,6 @@ data class ClockConfig(
/** Transition to AOD should move smartspace like large clock instead of small clock */
val useAlternateSmartspaceAODTransition: Boolean = false,
- /** Deprecated version of isReactiveToTone; moved to ClockPickerConfig */
- @Deprecated("TODO(b/352049256): Remove in favor of ClockPickerConfig.isReactiveToTone")
- val isReactiveToTone: Boolean = true,
-
/** True if the clock is large frame clock, which will use weather in compose. */
val useCustomClockScene: Boolean = false,
)
diff --git a/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt b/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
index d93f7d3093b8..81156d9698d8 100644
--- a/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
+++ b/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
@@ -24,6 +24,7 @@ import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
import javax.lang.model.element.PackageElement
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeKind
@@ -183,11 +184,17 @@ class ProtectedPluginProcessor : AbstractProcessor() {
// Method implementations
for (method in methods) {
val methodName = method.simpleName
+ if (methods.any { methodName.startsWith("${it.simpleName}\$") }) {
+ continue
+ }
val returnTypeName = method.returnType.toString()
val callArgs = StringBuilder()
var isFirst = true
+ val isStatic = method.modifiers.contains(Modifier.STATIC)
- line("@Override")
+ if (!isStatic) {
+ line("@Override")
+ }
parenBlock("public $returnTypeName $methodName") {
// While copying the method signature for the proxy type, we also
// accumulate arguments for the nested callsite.
@@ -202,7 +209,8 @@ class ProtectedPluginProcessor : AbstractProcessor() {
}
val isVoid = method.returnType.kind == TypeKind.VOID
- val nestedCall = "mInstance.$methodName($callArgs)"
+ val methodContainer = if (isStatic) sourceName else "mInstance"
+ val nestedCall = "$methodContainer.$methodName($callArgs)"
val callStatement =
when {
isVoid -> "$nestedCall;"
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_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 1f7889214bd5..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>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d445ed927a25..80fb8b9fcebc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2309,6 +2309,9 @@
<string name="group_system_lock_screen">Lock screen</string>
<!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] -->
<string name="group_system_quick_memo">Take a note</string>
+ <!-- TODO(b/383734125): make it translatable once string is finalized by UXW.-->
+ <!-- User visible title for the keyboard shortcut that toggles Voice Access. [CHAR LIMIT=70] -->
+ <string name="group_system_toggle_voice_access" translatable="false">Toggle Voice Access</string>
<!-- User visible title for the multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_system_multitasking">Multitasking</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 51892aac606a..ff6bcdb150f8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -19,6 +19,7 @@ package com.android.systemui.shared.system;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
+import android.window.TransitionInfo;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -30,7 +31,7 @@ public interface RecentsAnimationListener {
*/
void onAnimationStart(RecentsAnimationControllerCompat controller,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
- Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras);
+ Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras, TransitionInfo info);
/**
* Called when the animation into Recents was canceled. This call is made on the binder thread.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index acfa08643b63..c7ae02b61bff 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -142,33 +142,28 @@ public class KeyguardDisplayManager {
private boolean isKeyguardShowable(Display display) {
if (display == null) {
- if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display");
+ Log.i(TAG, "Cannot show Keyguard on null display");
return false;
}
if (ShadeWindowGoesAround.isEnabled()) {
int shadeDisplayId = mShadePositionRepositoryProvider.get().getDisplayId().getValue();
if (display.getDisplayId() == shadeDisplayId) {
- if (DEBUG) {
- Log.i(TAG,
- "Do not show KeyguardPresentation on the shade window display");
- }
+ Log.i(TAG, "Do not show KeyguardPresentation on the shade window display");
return false;
}
} else {
if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
- if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
+ Log.i(TAG, "Do not show KeyguardPresentation on the default display");
return false;
}
}
display.getDisplayInfo(mTmpDisplayInfo);
if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) {
- if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display");
+ Log.i(TAG, "Do not show KeyguardPresentation on a private display");
return false;
}
if ((mTmpDisplayInfo.flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) {
- if (DEBUG) {
- Log.i(TAG, "Do not show KeyguardPresentation on an unlocked display");
- }
+ Log.i(TAG, "Do not show KeyguardPresentation on an unlocked display");
return false;
}
@@ -176,14 +171,11 @@ public class KeyguardDisplayManager {
mDeviceStateHelper.isConcurrentDisplayActive(display)
|| mDeviceStateHelper.isRearDisplayOuterDefaultActive(display);
if (mKeyguardStateController.isOccluded() && deviceStateOccludesKeyguard) {
- if (DEBUG) {
- // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the
- // Keyguard state becomes "occluded". In this case, we should not show the
- // KeyguardPresentation, since the activity is presenting content onto the
- // non-default display.
- Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent or rear"
- + " display is active");
- }
+ // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the Keyguard
+ // state becomes "occluded". In this case, we should not show the KeyguardPresentation,
+ // since the activity is presenting content onto the non-default display.
+ Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent or rear"
+ + " display is active");
return false;
}
@@ -197,7 +189,7 @@ public class KeyguardDisplayManager {
*/
private boolean showPresentation(Display display) {
if (!isKeyguardShowable(display)) return false;
- if (DEBUG) Log.i(TAG, "Keyguard enabled on display: " + display);
+ Log.i(TAG, "Keyguard enabled on display: " + display);
final int displayId = display.getDisplayId();
Presentation presentation = mPresentations.get(displayId);
if (presentation == null) {
@@ -239,7 +231,7 @@ public class KeyguardDisplayManager {
public void show() {
if (!mShowing) {
- if (DEBUG) Log.v(TAG, "show");
+ Log.v(TAG, "show");
if (mMediaRouter != null) {
mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
@@ -253,7 +245,7 @@ public class KeyguardDisplayManager {
public void hide() {
if (mShowing) {
- if (DEBUG) Log.v(TAG, "hide");
+ Log.v(TAG, "hide");
if (mMediaRouter != null) {
mMediaRouter.removeCallback(mMediaRouterCallback);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a703b027b691..7d291c311ca3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2591,6 +2591,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
+ * @return true if optical udfps HW is supported on this device. Can return true even if the
+ * user has not enrolled udfps. This may be false if called before
+ * onAllAuthenticatorsRegistered.
+ */
+ public boolean isOpticalUdfpsSupported() {
+ return mAuthController.isOpticalUdfpsSupported();
+ }
+
+ /**
* @return true if there's at least one sfps enrollment for the current user.
*/
public boolean isSfpsEnrolled() {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index f530522fb707..5f79c8cada45 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -100,7 +100,8 @@ public abstract class SystemUIInitializer {
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
.setBackAnimation(mWMComponent.getBackAnimation())
- .setDesktopMode(mWMComponent.getDesktopMode());
+ .setDesktopMode(mWMComponent.getDesktopMode())
+ .setAppZoomOut(mWMComponent.getAppZoomOut());
// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
@@ -121,7 +122,8 @@ public abstract class SystemUIInitializer {
.setStartingSurface(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
.setBackAnimation(Optional.ofNullable(null))
- .setDesktopMode(Optional.ofNullable(null));
+ .setDesktopMode(Optional.ofNullable(null))
+ .setAppZoomOut(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d0cb507789a1..eee5f9e34317 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1011,6 +1011,16 @@ public class AuthController implements
}
/**
+ * @return true if optical udfps HW is supported on this device. Can return true even if
+ * the user has not enrolled udfps. This may be false if called before
+ * onAllAuthenticatorsRegistered.
+ */
+ public boolean isOpticalUdfpsSupported() {
+ return getUdfpsProps() != null && !getUdfpsProps().isEmpty() && getUdfpsProps()
+ .get(0).isOpticalUdfps();
+ }
+
+ /**
* @return true if ultrasonic udfps HW is supported on this device. Can return true even if
* the user has not enrolled udfps. This may be false if called before
* onAllAuthenticatorsRegistered.
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
index 4dc2a13480f5..0303048436c9 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -104,6 +104,31 @@ constructor(
}
}
+ override suspend fun onActionIconClick(deviceItem: DeviceItem, onIntent: (Intent) -> Unit) {
+ withContext(backgroundDispatcher) {
+ if (!audioSharingInteractor.audioSharingAvailable()) {
+ return@withContext deviceItemActionInteractorImpl.onActionIconClick(
+ deviceItem,
+ onIntent,
+ )
+ }
+
+ when (deviceItem.type) {
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.CHECK_MARK_ACTION_BUTTON_CLICKED)
+ audioSharingInteractor.stopAudioSharing()
+ }
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.PLUS_ACTION_BUTTON_CLICKED)
+ audioSharingInteractor.startAudioSharing()
+ }
+ else -> {
+ deviceItemActionInteractorImpl.onActionIconClick(deviceItem, onIntent)
+ }
+ }
+ }
+ }
+
private fun inSharingAndDeviceNoSource(
inAudioSharing: Boolean,
deviceItem: DeviceItem,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index c4f26cd46bf8..116e76c82008 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -29,6 +29,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
@@ -54,6 +55,8 @@ interface AudioSharingInteractor {
suspend fun startAudioSharing()
+ suspend fun stopAudioSharing()
+
suspend fun audioSharingAvailable(): Boolean
suspend fun qsDialogImprovementAvailable(): Boolean
@@ -61,7 +64,7 @@ interface AudioSharingInteractor {
@SysUISingleton
@OptIn(ExperimentalCoroutinesApi::class)
-class AudioSharingInteractorImpl
+open class AudioSharingInteractorImpl
@Inject
constructor(
private val context: Context,
@@ -99,6 +102,9 @@ constructor(
if (audioSharingAvailable()) {
audioSharingRepository.leAudioBroadcastProfile?.let { profile ->
isAudioSharingOn
+ // Skip the default value, we only care about adding source for newly
+ // started audio sharing session
+ .drop(1)
.mapNotNull { audioSharingOn ->
if (audioSharingOn) {
// onBroadcastMetadataChanged could emit multiple times during one
@@ -145,6 +151,13 @@ constructor(
audioSharingRepository.startAudioSharing()
}
+ override suspend fun stopAudioSharing() {
+ if (!audioSharingAvailable()) {
+ return
+ }
+ audioSharingRepository.stopAudioSharing()
+ }
+
// TODO(b/367965193): Move this after flags rollout
override suspend fun audioSharingAvailable(): Boolean {
return audioSharingRepository.audioSharingAvailable()
@@ -181,6 +194,8 @@ class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingIntera
override suspend fun startAudioSharing() {}
+ override suspend fun stopAudioSharing() {}
+
override suspend fun audioSharingAvailable(): Boolean = false
override suspend fun qsDialogImprovementAvailable(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
index b9b8d36d41e6..44f9769f5930 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
@@ -45,6 +45,8 @@ interface AudioSharingRepository {
suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice)
suspend fun startAudioSharing()
+
+ suspend fun stopAudioSharing()
}
@SysUISingleton
@@ -100,6 +102,15 @@ class AudioSharingRepositoryImpl(
leAudioBroadcastProfile?.startPrivateBroadcast()
}
}
+
+ override suspend fun stopAudioSharing() {
+ withContext(backgroundDispatcher) {
+ if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
+ return@withContext
+ }
+ leAudioBroadcastProfile?.stopLatestBroadcast()
+ }
+ }
}
@SysUISingleton
@@ -117,4 +128,6 @@ class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
override suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice) {}
override suspend fun startAudioSharing() {}
+
+ override suspend fun stopAudioSharing() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index b294dd1b0b71..56caddfbd637 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -56,6 +56,13 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
+data class DeviceItemClick(val deviceItem: DeviceItem, val clickedView: View, val target: Target) {
+ enum class Target {
+ ENTIRE_ROW,
+ ACTION_ICON,
+ }
+}
+
/** Dialog for showing active, connected and saved bluetooth devices. */
class BluetoothTileDialogDelegate
@AssistedInject
@@ -80,7 +87,7 @@ internal constructor(
internal val bluetoothAutoOnToggle
get() = mutableBluetoothAutoOnToggle.asStateFlow()
- private val mutableDeviceItemClick: MutableSharedFlow<DeviceItem> =
+ private val mutableDeviceItemClick: MutableSharedFlow<DeviceItemClick> =
MutableSharedFlow(extraBufferCapacity = 1)
internal val deviceItemClick
get() = mutableDeviceItemClick.asSharedFlow()
@@ -90,7 +97,7 @@ internal constructor(
internal val contentHeight
get() = mutableContentHeight.asSharedFlow()
- private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback)
+ private val deviceItemAdapter: Adapter = Adapter()
private var lastUiUpdateMs: Long = -1
@@ -334,8 +341,7 @@ internal constructor(
}
}
- internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
- RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
+ internal inner class Adapter : RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
private val diffUtilCallback =
object : DiffUtil.ItemCallback<DeviceItem>() {
@@ -376,7 +382,7 @@ internal constructor(
override fun onBindViewHolder(holder: DeviceItemViewHolder, position: Int) {
val item = getItem(position)
- holder.bind(item, onClickCallback)
+ holder.bind(item)
}
internal fun getItem(position: Int) = asyncListDiffer.currentList[position]
@@ -390,19 +396,18 @@ internal constructor(
private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
- private val iconGear = view.requireViewById<ImageView>(R.id.gear_icon_image)
- private val gearView = view.requireViewById<View>(R.id.gear_icon)
+ private val actionIcon = view.requireViewById<ImageView>(R.id.gear_icon_image)
+ private val actionIconView = view.requireViewById<View>(R.id.gear_icon)
private val divider = view.requireViewById<View>(R.id.divider)
- internal fun bind(
- item: DeviceItem,
- deviceItemOnClickCallback: BluetoothTileDialogCallback,
- ) {
+ internal fun bind(item: DeviceItem) {
container.apply {
isEnabled = item.isEnabled
background = item.background?.let { context.getDrawable(it) }
setOnClickListener {
- mutableDeviceItemClick.tryEmit(item)
+ mutableDeviceItemClick.tryEmit(
+ DeviceItemClick(item, it, DeviceItemClick.Target.ENTIRE_ROW)
+ )
uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
}
@@ -421,7 +426,8 @@ internal constructor(
}
}
- iconGear.apply { drawable?.let { it.mutate()?.setTint(tintColor) } }
+ actionIcon.setImageResource(item.actionIconRes)
+ actionIcon.drawable?.setTint(tintColor)
divider.setBackgroundColor(tintColor)
@@ -454,8 +460,10 @@ internal constructor(
nameView.text = item.deviceName
summaryView.text = item.connectionSummary
- gearView.setOnClickListener {
- deviceItemOnClickCallback.onDeviceItemGearClicked(item, it)
+ actionIconView.setOnClickListener {
+ mutableDeviceItemClick.tryEmit(
+ DeviceItemClick(item, it, DeviceItemClick.Target.ACTION_ICON)
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index aad233fe40ca..7c66ec059e64 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -49,7 +49,7 @@ enum class BluetoothTileDialogUiEvent(val metricId: Int) : UiEventLogger.UiEvent
LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED(1719),
@Deprecated(
"Use case no longer needed",
- ReplaceWith("LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED")
+ ReplaceWith("LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED"),
)
@UiEvent(doc = "Not broadcasting, one of the two connected LE audio devices is clicked")
LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720),
@@ -59,7 +59,11 @@ enum class BluetoothTileDialogUiEvent(val metricId: Int) : UiEventLogger.UiEvent
@UiEvent(doc = "Clicked on switch active button on audio sharing dialog")
AUDIO_SHARING_DIALOG_SWITCH_ACTIVE_CLICKED(1890),
@UiEvent(doc = "Clicked on share audio button on audio sharing dialog")
- AUDIO_SHARING_DIALOG_SHARE_AUDIO_CLICKED(1891);
+ AUDIO_SHARING_DIALOG_SHARE_AUDIO_CLICKED(1891),
+ @UiEvent(doc = "Clicked on plus action button")
+ PLUS_ACTION_BUTTON_CLICKED(2061),
+ @UiEvent(doc = "Clicked on checkmark action button")
+ CHECK_MARK_ACTION_BUTTON_CLICKED(2062);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index 497d8cf2e159..9460e7c2c8d5 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -35,7 +35,6 @@ import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
import com.android.systemui.dagger.SysUISingleton
@@ -227,8 +226,22 @@ constructor(
// deviceItemClick is emitted when user clicked on a device item.
dialogDelegate.deviceItemClick
.onEach {
- deviceItemActionInteractor.onClick(it, dialog)
- logger.logDeviceClick(it.cachedBluetoothDevice.address, it.type)
+ when (it.target) {
+ DeviceItemClick.Target.ENTIRE_ROW -> {
+ deviceItemActionInteractor.onClick(it.deviceItem, dialog)
+ logger.logDeviceClick(
+ it.deviceItem.cachedBluetoothDevice.address,
+ it.deviceItem.type,
+ )
+ }
+
+ DeviceItemClick.Target.ACTION_ICON -> {
+ deviceItemActionInteractor.onActionIconClick(it.deviceItem) { intent
+ ->
+ startSettingsActivity(intent, it.clickedView)
+ }
+ }
+ }
}
.launchIn(this)
@@ -287,20 +300,6 @@ constructor(
)
}
- override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) {
- uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_GEAR_CLICKED)
- val intent =
- Intent(ACTION_BLUETOOTH_DEVICE_DETAILS).apply {
- putExtra(
- EXTRA_SHOW_FRAGMENT_ARGUMENTS,
- Bundle().apply {
- putString("device_address", deviceItem.cachedBluetoothDevice.address)
- },
- )
- }
- startSettingsActivity(intent, view)
- }
-
override fun onSeeAllClicked(view: View) {
uiEventLogger.log(BluetoothTileDialogUiEvent.SEE_ALL_CLICKED)
startSettingsActivity(Intent(ACTION_PREVIOUSLY_CONNECTED_DEVICE), view)
@@ -382,8 +381,6 @@ constructor(
}
interface BluetoothTileDialogCallback {
- fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
-
fun onSeeAllClicked(view: View)
fun onPairNewDeviceClicked(view: View)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index 2ba4c73a0293..f7af16d99fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -53,5 +53,6 @@ data class DeviceItem(
val background: Int? = null,
var isEnabled: Boolean = true,
var actionAccessibilityLabel: String = "",
- var isActive: Boolean = false
+ var isActive: Boolean = false,
+ val actionIconRes: Int = -1,
)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index 2b55e1c51f5f..cb4ec37a1a66 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.bluetooth.qsdialog
+import android.content.Intent
+import android.os.Bundle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -25,7 +27,9 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
interface DeviceItemActionInteractor {
- suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {}
+ suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog)
+
+ suspend fun onActionIconClick(deviceItem: DeviceItem, onIntent: (Intent) -> Unit)
}
@SysUISingleton
@@ -67,4 +71,44 @@ constructor(
}
}
}
+
+ override suspend fun onActionIconClick(deviceItem: DeviceItem, onIntent: (Intent) -> Unit) {
+ withContext(backgroundDispatcher) {
+ deviceItem.cachedBluetoothDevice.apply {
+ when (deviceItem.type) {
+ DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
+ DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_GEAR_CLICKED)
+ val intent =
+ Intent(ACTION_BLUETOOTH_DEVICE_DETAILS).apply {
+ putExtra(
+ EXTRA_SHOW_FRAGMENT_ARGUMENTS,
+ Bundle().apply {
+ putString(
+ "device_address",
+ deviceItem.cachedBluetoothDevice.address,
+ )
+ },
+ )
+ }
+ onIntent(intent)
+ }
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ throw IllegalArgumentException("Invalid device type: ${deviceItem.type}")
+ // Throw exception. Should already be handled in
+ // AudioSharingDeviceItemActionInteractor.
+ }
+ }
+ }
+ }
+ }
+
+ private companion object {
+ const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
+ const val ACTION_BLUETOOTH_DEVICE_DETAILS =
+ "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 92f05803f7cf..095e6e741584 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -30,6 +30,8 @@ private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
private val connected = R.string.quick_settings_bluetooth_device_connected
private val audioSharing = R.string.quick_settings_bluetooth_device_audio_sharing
+private val audioSharingAddIcon = R.drawable.ic_add
+private val audioSharingOnGoingIcon = R.drawable.ic_check
private val saved = R.string.quick_settings_bluetooth_device_saved
private val actionAccessibilityLabelActivate =
R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
@@ -63,6 +65,7 @@ abstract class DeviceItemFactory {
background: Int,
actionAccessibilityLabel: String,
isActive: Boolean,
+ actionIconRes: Int = R.drawable.ic_settings_24dp,
): DeviceItem {
return DeviceItem(
type = type,
@@ -75,6 +78,7 @@ abstract class DeviceItemFactory {
isEnabled = !cachedDevice.isBusy,
actionAccessibilityLabel = actionAccessibilityLabel,
isActive = isActive,
+ actionIconRes = actionIconRes,
)
}
}
@@ -125,6 +129,7 @@ internal class AudioSharingMediaDeviceItemFactory(
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
"",
isActive = !cachedDevice.isBusy,
+ actionIconRes = audioSharingOnGoingIcon,
)
}
}
@@ -156,6 +161,7 @@ internal class AvailableAudioSharingMediaDeviceItemFactory(
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
"",
isActive = false,
+ actionIconRes = audioSharingAddIcon,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
index 78156dbc8964..ca49de3b1510 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt
@@ -25,6 +25,7 @@ import com.android.compose.animation.scene.TransitionKey
*/
object CommunalTransitionKeys {
/** Fades the glanceable hub without any translation */
+ @Deprecated("No longer supported as all hub transitions will be fades.")
val SimpleFade = TransitionKey("SimpleFade")
/** Transition from the glanceable hub before entering edit mode */
val ToEditMode = TransitionKey("ToEditMode")
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 00eead6eb7fc..555fe6ef157d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.phone.ConfigurationForwarder;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.wm.shell.appzoomout.AppZoomOut;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -115,6 +116,9 @@ public interface SysUIComponent {
@BindsInstance
Builder setDesktopMode(Optional<DesktopMode> d);
+ @BindsInstance
+ Builder setAppZoomOut(Optional<AppZoomOut> a);
+
SysUIComponent build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 41a59a959771..ae6238724042 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -50,6 +51,8 @@ class DeviceEntryHapticsInteractor
constructor(
biometricSettingsRepository: BiometricSettingsRepository,
deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
+ deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ keyguardBypassInteractor: KeyguardBypassInteractor,
deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
fingerprintPropertyRepository: FingerprintPropertyRepository,
@@ -82,7 +85,7 @@ constructor(
emit(recentPowerButtonPressThresholdMs * -1L - 1L)
}
- val playSuccessHaptic: Flow<Unit> =
+ private val playHapticsOnDeviceEntry: Flow<Boolean> =
deviceEntrySourceInteractor.deviceEntryFromBiometricSource
.sample(
combine(
@@ -92,17 +95,29 @@ constructor(
::Triple,
)
)
- .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+ .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
val sideFpsAllowsHaptic =
!powerButtonDown &&
systemClock.uptimeMillis() - lastPowerButtonWakeup >
recentPowerButtonPressThresholdMs
val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
if (!allowHaptic) {
- logger.d("Skip success haptic. Recent power button press or button is down.")
+ logger.d(
+ "Skip success entry haptic from power button. Recent power button press or button is down."
+ )
}
allowHaptic
}
+
+ private val playHapticsOnFaceAuthSuccessAndBypassDisabled: Flow<Boolean> =
+ deviceEntryFaceAuthInteractor.isAuthenticated
+ .filter { it }
+ .sample(keyguardBypassInteractor.isBypassAvailable)
+ .map { !it }
+
+ val playSuccessHaptic: Flow<Unit> =
+ merge(playHapticsOnDeviceEntry, playHapticsOnFaceAuthSuccessAndBypassDisabled)
+ .filter { it }
// map to Unit
.map {}
.dumpWhileCollecting("playSuccessHaptic")
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index 21002c676ec6..d7a4dba3188a 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -278,11 +278,11 @@ constructor(
}
private suspend fun hasInitialDelayElapsed(deviceType: DeviceType): Boolean {
- val oobeLaunchTime =
- tutorialRepository.getScheduledTutorialLaunchTime(deviceType) ?: return false
- return clock
- .instant()
- .isAfter(oobeLaunchTime.plusSeconds(initialDelayDuration.inWholeSeconds))
+ val oobeTime =
+ tutorialRepository.getScheduledTutorialLaunchTime(deviceType)
+ ?: tutorialRepository.getNotifiedTime(deviceType)
+ ?: return false
+ return clock.instant().isAfter(oobeTime.plusSeconds(initialDelayDuration.inWholeSeconds))
}
private data class StatsUpdateRequest(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 129a6bb72996..2ed0671a570b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -23,13 +23,7 @@ import com.android.server.notification.Flags.crossAppPoliteNotifications
import com.android.server.notification.Flags.politeNotifications
import com.android.server.notification.Flags.vibrateWhileUnlocked
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
-import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
-import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
import com.android.systemui.Flags.communalHub
-import com.android.systemui.Flags.statusBarCallChipNotificationIcon
-import com.android.systemui.Flags.statusBarScreenSharingChips
-import com.android.systemui.Flags.statusBarUseReposForCallChip
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shared.flag.DualShade
@@ -63,10 +57,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
// DualShade dependencies
DualShade.token dependsOn SceneContainerFlag.getMainAconfigFlag()
-
- // Status bar chip dependencies
- statusBarCallChipNotificationIconToken dependsOn statusBarUseReposForCallChipToken
- statusBarCallChipNotificationIconToken dependsOn statusBarScreenSharingChipsToken
}
private inline val politeNotifications
@@ -86,17 +76,4 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
private inline val communalHub
get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
-
- private inline val statusBarCallChipNotificationIconToken
- get() =
- FlagToken(
- FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON,
- statusBarCallChipNotificationIcon(),
- )
-
- private inline val statusBarScreenSharingChipsToken
- get() = FlagToken(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, statusBarScreenSharingChips())
-
- private inline val statusBarUseReposForCallChipToken
- get() = FlagToken(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP, statusBarUseReposForCallChip())
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index e1ebf7cdf472..cf5c3402792e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -463,6 +463,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener);
mGlobalSettings.unregisterContentObserverSync(mAirplaneModeObserver);
mConfigurationController.removeCallback(this);
+ if (mShowSilentToggle) {
+ mRingerModeTracker.getRingerMode().removeObservers(this);
+ }
}
protected Context getContext() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index e255bdea6100..6a42cdc876ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -41,6 +41,8 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVI
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS
+import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
@@ -63,6 +65,7 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to System,
KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to System,
KEY_GESTURE_TYPE_ALL_APPS to System,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to System,
// Multitasking Category
KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to MultiTasking,
@@ -100,6 +103,7 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.shortcut_helper_category_system_apps,
KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to
R.string.shortcut_helper_category_system_apps,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.shortcut_helper_category_system_apps,
// Multitasking Category
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
@@ -148,6 +152,7 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.group_system_access_google_assistant,
KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to
R.string.group_system_access_google_assistant,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.group_system_toggle_voice_access,
// Multitasking Category
KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.group_system_cycle_forward,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index 5060abdda247..c3c9df97a682 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -32,12 +32,14 @@ import android.view.KeyEvent.KEYCODE_RECENT_APPS
import android.view.KeyEvent.KEYCODE_S
import android.view.KeyEvent.KEYCODE_SLASH
import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.KEYCODE_V
import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
+import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures
import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
@@ -118,8 +120,8 @@ constructor(@Main private val resources: Resources, private val inputManager: In
return shortcuts
}
- private fun systemControlsShortcuts() =
- listOf(
+ private fun systemControlsShortcuts(): List<KeyboardShortcutInfo> {
+ val shortcuts = mutableListOf(
// Access list of all apps and search (i.e. Search/Launcher):
// - Meta
shortcutInfo(resources.getString(R.string.group_system_access_all_apps_search)) {
@@ -176,6 +178,19 @@ constructor(@Main private val resources: Resources, private val inputManager: In
},
)
+ if (enableVoiceAccessKeyGestures()) {
+ shortcuts.add(
+ // Toggle voice access:
+ // - Meta + Alt + V
+ shortcutInfo(resources.getString(R.string.group_system_toggle_voice_access)) {
+ command(META_META_ON or META_ALT_ON, KEYCODE_V)
+ }
+ )
+ }
+
+ return shortcuts
+ }
+
private fun systemAppsShortcuts() =
listOf(
// Pull up Notes app for quick memo:
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/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/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/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/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index c34edc81bfe7..30d1f05771d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -118,7 +118,8 @@ constructor(
override fun addCallback(callback: QSTile.Callback?) {
callback ?: return
callbacks.add(callback)
- state?.let(callback::onStateChanged)
+ state.copyTo(cachedState)
+ state.let(callback::onStateChanged)
}
override fun removeCallback(callback: QSTile.Callback?) {
@@ -212,9 +213,9 @@ constructor(
qsTileViewModel.destroy()
}
- override fun getState(): QSTile.State =
+ override fun getState(): QSTile.AdapterState =
qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
- ?: QSTile.State()
+ ?: QSTile.AdapterState()
override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
@@ -241,7 +242,7 @@ constructor(
context: Context,
viewModelState: QSTileState,
config: QSTileConfig,
- ): QSTile.State =
+ ): QSTile.AdapterState =
// we have to use QSTile.BooleanState to support different side icons
// which are bound to instanceof QSTile.BooleanState in QSTileView.
QSTile.AdapterState().apply {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index d60f05e685bb..0488962fd076 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -90,22 +90,15 @@ constructor(
initialValue = defaultTransitionState,
)
- fun changeScene(
- toScene: SceneKey,
- transitionKey: TransitionKey? = null,
- ) {
- dataSource.changeScene(
- toScene = toScene,
- transitionKey = transitionKey,
- )
+ /** Number of currently active transition animations. */
+ val activeTransitionAnimationCount = MutableStateFlow(0)
+
+ fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null) {
+ dataSource.changeScene(toScene = toScene, transitionKey = transitionKey)
}
- fun snapToScene(
- toScene: SceneKey,
- ) {
- dataSource.snapToScene(
- toScene = toScene,
- )
+ fun snapToScene(toScene: SceneKey) {
+ dataSource.snapToScene(toScene = toScene)
}
/**
@@ -116,10 +109,7 @@ constructor(
* [overlay] is already shown.
*/
fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey? = null) {
- dataSource.showOverlay(
- overlay = overlay,
- transitionKey = transitionKey,
- )
+ dataSource.showOverlay(overlay = overlay, transitionKey = transitionKey)
}
/**
@@ -130,10 +120,7 @@ constructor(
* if [overlay] is already hidden.
*/
fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey? = null) {
- dataSource.hideOverlay(
- overlay = overlay,
- transitionKey = transitionKey,
- )
+ dataSource.hideOverlay(overlay = overlay, transitionKey = transitionKey)
}
/**
@@ -143,11 +130,7 @@ constructor(
* This throws if [from] is not currently shown or if [to] is already shown.
*/
fun replaceOverlay(from: OverlayKey, to: OverlayKey, transitionKey: TransitionKey? = null) {
- dataSource.replaceOverlay(
- from = from,
- to = to,
- transitionKey = transitionKey,
- )
+ dataSource.replaceOverlay(from = from, to = to, transitionKey = transitionKey)
}
/** Sets whether the container is visible. */
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 0e6fc36fb96a..ba9dc7625769 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
/**
* Generic business logic and app state accessors for the scene framework.
@@ -165,12 +166,15 @@ constructor(
/** Whether the scene container is visible. */
val isVisible: StateFlow<Boolean> =
- combine(repository.isVisible, repository.isRemoteUserInputOngoing) {
- isVisible,
- isRemoteUserInteractionOngoing ->
+ combine(
+ repository.isVisible,
+ repository.isRemoteUserInputOngoing,
+ repository.activeTransitionAnimationCount,
+ ) { isVisible, isRemoteUserInteractionOngoing, activeTransitionAnimationCount ->
isVisibleInternal(
raw = isVisible,
isRemoteUserInputOngoing = isRemoteUserInteractionOngoing,
+ activeTransitionAnimationCount = activeTransitionAnimationCount,
)
}
.stateIn(
@@ -436,8 +440,9 @@ constructor(
private fun isVisibleInternal(
raw: Boolean = repository.isVisible.value,
isRemoteUserInputOngoing: Boolean = repository.isRemoteUserInputOngoing.value,
+ activeTransitionAnimationCount: Int = repository.activeTransitionAnimationCount.value,
): Boolean {
- return raw || isRemoteUserInputOngoing
+ return raw || isRemoteUserInputOngoing || activeTransitionAnimationCount > 0
}
/**
@@ -525,4 +530,50 @@ constructor(
): Flow<Map<UserAction, UserActionResult>> {
return disabledContentInteractor.filteredUserActions(unfiltered)
}
+
+ /**
+ * Notifies that a transition animation has started.
+ *
+ * The scene container will remain visible while any transition animation is running within it.
+ */
+ fun onTransitionAnimationStart() {
+ repository.activeTransitionAnimationCount.update { current ->
+ (current + 1).also {
+ check(it < 10) {
+ "Number of active transition animations is too high. Something must be" +
+ " calling onTransitionAnimationStart too many times!"
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies that a transition animation has ended.
+ *
+ * The scene container will remain visible while any transition animation is running within it.
+ */
+ fun onTransitionAnimationEnd() {
+ decrementActiveTransitionAnimationCount()
+ }
+
+ /**
+ * Notifies that a transition animation has been canceled.
+ *
+ * The scene container will remain visible while any transition animation is running within it.
+ */
+ fun onTransitionAnimationCancelled() {
+ decrementActiveTransitionAnimationCount()
+ }
+
+ private fun decrementActiveTransitionAnimationCount() {
+ repository.activeTransitionAnimationCount.update { current ->
+ (current - 1).also {
+ check(it >= 0) {
+ "Number of active transition animations is negative. Something must be" +
+ " calling onTransitionAnimationEnd or onTransitionAnimationCancelled too" +
+ " many times!"
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 8d8c24eae9e2..3a23a71cf7bf 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -25,6 +25,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.AuthInteractionProperties
import com.android.systemui.CoreStartable
import com.android.systemui.Flags
+import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -146,6 +147,7 @@ constructor(
private val vibratorHelper: VibratorHelper,
private val msdlPlayer: MSDLPlayer,
private val disabledContentInteractor: DisabledContentInteractor,
+ private val activityTransitionAnimator: ActivityTransitionAnimator,
) : CoreStartable {
private val centralSurfaces: CentralSurfaces?
get() = centralSurfacesOptLazy.get().getOrNull()
@@ -169,6 +171,7 @@ constructor(
handleKeyguardEnabledness()
notifyKeyguardDismissCancelledCallbacks()
refreshLockscreenEnabled()
+ hydrateActivityTransitionAnimationState()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -929,6 +932,35 @@ constructor(
}
}
+ /**
+ * Wires the scene framework to activity transition animations that originate from anywhere. A
+ * subset of these may actually originate from UI inside one of the scenes in the framework.
+ *
+ * Telling the scene framework about ongoing activity transition animations is critical so the
+ * scene framework doesn't make its scene container invisible during a transition.
+ *
+ * As it turns out, making the scene container view invisible during a transition animation
+ * disrupts the animation and causes interaction jank CUJ tracking to ignore reports of the CUJ
+ * ending or being canceled.
+ */
+ private fun hydrateActivityTransitionAnimationState() {
+ activityTransitionAnimator.addListener(
+ object : ActivityTransitionAnimator.Listener {
+ override fun onTransitionAnimationStart() {
+ sceneInteractor.onTransitionAnimationStart()
+ }
+
+ override fun onTransitionAnimationEnd() {
+ sceneInteractor.onTransitionAnimationEnd()
+ }
+
+ override fun onTransitionAnimationCancelled() {
+ sceneInteractor.onTransitionAnimationCancelled()
+ }
+ }
+ )
+ }
+
companion object {
private const val TAG = "SceneContainerStartable"
}
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/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2bcd3fcfed17..10b726b90894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -92,6 +92,7 @@ import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Inject;
@@ -297,6 +298,9 @@ public class NotificationLockscreenUserManagerImpl implements
// The last lock time. Uses currentTimeMillis
@VisibleForTesting
protected final AtomicLong mLastLockTime = new AtomicLong(-1);
+ // Whether or not the device is locked
+ @VisibleForTesting
+ protected final AtomicBoolean mLocked = new AtomicBoolean(true);
protected int mCurrentUserId = 0;
@@ -369,6 +373,7 @@ public class NotificationLockscreenUserManagerImpl implements
if (!unlocked) {
mLastLockTime.set(System.currentTimeMillis());
}
+ mLocked.set(!unlocked);
}));
}
}
@@ -737,7 +742,7 @@ public class NotificationLockscreenUserManagerImpl implements
return false;
}
- if (!mKeyguardManager.isDeviceLocked()) {
+ if (!mLocked.get()) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index e83cded4e2ce..38f7c39203f0 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,7 +32,9 @@ 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.Flags.spatialModelAppPushback
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -52,7 +53,9 @@ import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.WallpaperController
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.systemui.window.flag.WindowBlurFlag
+import com.android.wm.shell.appzoomout.AppZoomOut
import java.io.PrintWriter
+import java.util.Optional
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.sign
@@ -79,6 +82,7 @@ constructor(
private val splitShadeStateController: SplitShadeStateController,
private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
@Application private val applicationScope: CoroutineScope,
+ private val appZoomOutOptional: Optional<AppZoomOut>,
dumpManager: DumpManager,
configurationController: ConfigurationController,
) : ShadeExpansionListener, Dumpable {
@@ -263,7 +267,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)
}
@@ -271,6 +275,13 @@ constructor(
private fun onBlurApplied(appliedBlurRadius: Int, zoomOutFromShadeRadius: Float) {
lastAppliedBlur = appliedBlurRadius
wallpaperController.setNotificationShadeZoom(zoomOutFromShadeRadius)
+ if (spatialModelAppPushback()) {
+ appZoomOutOptional.ifPresent { appZoomOut ->
+ appZoomOut.setProgress(
+ zoomOutFromShadeRadius
+ )
+ }
+ }
listeners.forEach {
it.onWallpaperZoomOutChanged(zoomOutFromShadeRadius)
it.onBlurRadiusChanged(appliedBlurRadius)
@@ -384,7 +395,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/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 48cf7a83c324..155049f512d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
import android.util.MathUtils;
@@ -30,6 +31,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -1014,12 +1016,24 @@ public class NotificationShelf extends ActivatableNotificationView {
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (mInteractive) {
+ // Add two accessibility actions that both performs expanding the notification shade
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
- AccessibilityNodeInfo.AccessibilityAction unlock
- = new AccessibilityNodeInfo.AccessibilityAction(
+
+ AccessibilityAction seeAll = new AccessibilityAction(
AccessibilityNodeInfo.ACTION_CLICK,
- getContext().getString(R.string.accessibility_overflow_action));
- info.addAction(unlock);
+ getContext().getString(R.string.accessibility_overflow_action)
+ );
+ info.addAction(seeAll);
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle args) {
+ // override ACTION_EXPAND with ACTION_CLICK
+ if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
+ return super.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, args);
+ } else {
+ return super.performAccessibilityAction(action, args);
}
}
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/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index de08e3891902..86954d569199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.chips.call.ui.viewmodel
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.Flags
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -64,18 +63,12 @@ constructor(
is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Hidden()
is OngoingCallModel.InCall -> {
val icon =
- if (
- Flags.statusBarCallChipNotificationIcon() &&
- state.notificationIconView != null
- ) {
+ if (state.notificationIconView != null) {
StatusBarConnectedDisplays.assertInLegacyMode()
OngoingActivityChipModel.ChipIcon.StatusBarView(
state.notificationIconView
)
- } else if (
- StatusBarConnectedDisplays.isEnabled &&
- Flags.statusBarCallChipNotificationIcon()
- ) {
+ } else if (StatusBarConnectedDisplays.isEnabled) {
OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
state.notificationKey
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 2f6431b05c8b..ec3a5b271e35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
+import com.android.systemui.statusbar.notification.domain.model.TopPinnedState
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import javax.inject.Inject
@@ -60,7 +61,7 @@ constructor(
/** Converts the notification to the [OngoingActivityChipModel] object. */
private fun NotificationChipModel.toActivityChipModel(
- headsUpState: PinnedStatus
+ headsUpState: TopPinnedState
): OngoingActivityChipModel.Shown {
StatusBarNotifChips.assertInNewMode()
val icon =
@@ -87,8 +88,12 @@ constructor(
}
}
- if (headsUpState == PinnedStatus.PinnedByUser) {
- // If the user tapped the chip to show the HUN, we want to just show the icon because
+ val isShowingHeadsUpFromChipTap =
+ headsUpState is TopPinnedState.Pinned &&
+ headsUpState.status == PinnedStatus.PinnedByUser &&
+ headsUpState.key == this.key
+ if (isShowingHeadsUpFromChipTap) {
+ // If the user tapped this chip to show the HUN, we want to just show the icon because
// the HUN will show the rest of the information.
return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 69ef09d8bf5e..b0fa9d842480 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -25,6 +25,7 @@ import android.widget.DateTimeView
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
+import androidx.annotation.UiThread
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -38,24 +39,24 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.Notificati
/** Binder for ongoing activity chip views. */
object OngoingActivityChipBinder {
/** Binds the given [chipModel] data to the given [chipView]. */
- fun bind(chipModel: OngoingActivityChipModel, chipView: View, iconViewStore: IconViewStore?) {
- val chipContext = chipView.context
- val chipDefaultIconView: ImageView =
- chipView.requireViewById(R.id.ongoing_activity_chip_icon)
- val chipTimeView: ChipChronometer =
- chipView.requireViewById(R.id.ongoing_activity_chip_time)
- val chipTextView: TextView = chipView.requireViewById(R.id.ongoing_activity_chip_text)
- val chipShortTimeDeltaView: DateTimeView =
- chipView.requireViewById(R.id.ongoing_activity_chip_short_time_delta)
- val chipBackgroundView: ChipBackgroundContainer =
- chipView.requireViewById(R.id.ongoing_activity_chip_background)
+ fun bind(
+ chipModel: OngoingActivityChipModel,
+ viewBinding: OngoingActivityChipViewBinding,
+ iconViewStore: IconViewStore?,
+ ) {
+ val chipContext = viewBinding.rootView.context
+ val chipDefaultIconView = viewBinding.defaultIconView
+ val chipTimeView = viewBinding.timeView
+ val chipTextView = viewBinding.textView
+ val chipShortTimeDeltaView = viewBinding.shortTimeDeltaView
+ val chipBackgroundView = viewBinding.backgroundView
when (chipModel) {
is OngoingActivityChipModel.Shown -> {
// Data
setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore)
setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView)
- chipView.setOnClickListener(chipModel.onClickListener)
+ viewBinding.rootView.setOnClickListener(chipModel.onClickListener)
updateChipPadding(
chipModel,
chipBackgroundView,
@@ -65,7 +66,7 @@ object OngoingActivityChipBinder {
)
// Accessibility
- setChipAccessibility(chipModel, chipView, chipBackgroundView)
+ setChipAccessibility(chipModel, viewBinding.rootView, chipBackgroundView)
// Colors
val textColor = chipModel.colors.text(chipContext)
@@ -83,6 +84,85 @@ object OngoingActivityChipBinder {
}
}
+ /** Stores [rootView] and relevant child views in an object for easy reference. */
+ fun createBinding(rootView: View): OngoingActivityChipViewBinding {
+ return OngoingActivityChipViewBinding(
+ rootView = rootView,
+ timeView = rootView.requireViewById(R.id.ongoing_activity_chip_time),
+ textView = rootView.requireViewById(R.id.ongoing_activity_chip_text),
+ shortTimeDeltaView =
+ rootView.requireViewById(R.id.ongoing_activity_chip_short_time_delta),
+ defaultIconView = rootView.requireViewById(R.id.ongoing_activity_chip_icon),
+ backgroundView = rootView.requireViewById(R.id.ongoing_activity_chip_background),
+ )
+ }
+
+ /**
+ * Resets any width restrictions that were placed on the primary chip's contents.
+ *
+ * Should be used when the user's screen bounds changed because there may now be more room in
+ * the status bar to show additional content.
+ */
+ fun resetPrimaryChipWidthRestrictions(
+ primaryChipViewBinding: OngoingActivityChipViewBinding,
+ currentPrimaryChipViewModel: OngoingActivityChipModel,
+ ) {
+ if (currentPrimaryChipViewModel is OngoingActivityChipModel.Hidden) {
+ return
+ }
+ resetChipMainContentWidthRestrictions(
+ primaryChipViewBinding,
+ currentPrimaryChipViewModel as OngoingActivityChipModel.Shown,
+ )
+ }
+
+ /**
+ * Resets any width restrictions that were placed on the secondary chip and its contents.
+ *
+ * Should be used when the user's screen bounds changed because there may now be more room in
+ * the status bar to show additional content.
+ */
+ fun resetSecondaryChipWidthRestrictions(
+ secondaryChipViewBinding: OngoingActivityChipViewBinding,
+ currentSecondaryChipModel: OngoingActivityChipModel,
+ ) {
+ if (currentSecondaryChipModel is OngoingActivityChipModel.Hidden) {
+ return
+ }
+ secondaryChipViewBinding.rootView.resetWidthRestriction()
+ resetChipMainContentWidthRestrictions(
+ secondaryChipViewBinding,
+ currentSecondaryChipModel as OngoingActivityChipModel.Shown,
+ )
+ }
+
+ private fun resetChipMainContentWidthRestrictions(
+ viewBinding: OngoingActivityChipViewBinding,
+ model: OngoingActivityChipModel.Shown,
+ ) {
+ when (model) {
+ is OngoingActivityChipModel.Shown.Text -> viewBinding.textView.resetWidthRestriction()
+ is OngoingActivityChipModel.Shown.Timer -> viewBinding.timeView.resetWidthRestriction()
+ is OngoingActivityChipModel.Shown.ShortTimeDelta ->
+ viewBinding.shortTimeDeltaView.resetWidthRestriction()
+ is OngoingActivityChipModel.Shown.IconOnly,
+ is OngoingActivityChipModel.Shown.Countdown -> {}
+ }
+ }
+
+ /**
+ * Resets any width restrictions that were placed on the given view.
+ *
+ * Should be used when the user's screen bounds changed because there may now be more room in
+ * the status bar to show additional content.
+ */
+ @UiThread
+ fun View.resetWidthRestriction() {
+ // View needs to be visible in order to be re-measured
+ visibility = View.VISIBLE
+ forceLayout()
+ }
+
private fun setChipIcon(
chipModel: OngoingActivityChipModel.Shown,
backgroundView: ChipBackgroundContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipViewBinding.kt
new file mode 100644
index 000000000000..1814b7430330
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipViewBinding.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.binder
+
+import android.view.View
+import android.widget.ImageView
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.statusbar.chips.ui.view.ChipDateTimeView
+import com.android.systemui.statusbar.chips.ui.view.ChipTextView
+
+/** Stores bound views for a given chip. */
+data class OngoingActivityChipViewBinding(
+ val rootView: View,
+ val timeView: ChipChronometer,
+ val textView: ChipTextView,
+ val shortTimeDeltaView: ChipDateTimeView,
+ val defaultIconView: ImageView,
+ val backgroundView: ChipBackgroundContainer,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
new file mode 100644
index 000000000000..1be5842bceeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.chips.ui.compose
+
+import android.content.res.ColorStateList
+import android.view.ViewGroup
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+
+@Composable
+fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) {
+ val context = LocalContext.current
+ val isClickable = model.onClickListener != null
+ val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView
+
+ // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
+ // height of the chip is determined by the height of the background of the Row below.
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier =
+ modifier
+ .fillMaxHeight()
+ .clickable(
+ enabled = isClickable,
+ onClick = {
+ // TODO(b/372657935): Implement click actions.
+ },
+ ),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.clip(
+ RoundedCornerShape(
+ dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
+ )
+ )
+ .height(dimensionResource(R.dimen.ongoing_appops_chip_height))
+ .widthIn(
+ min =
+ if (isClickable) {
+ dimensionResource(id = R.dimen.min_clickable_item_size)
+ } else {
+ 0.dp
+ }
+ )
+ .background(Color(model.colors.background(context).defaultColor))
+ .padding(
+ horizontal =
+ if (hasEmbeddedIcon) {
+ 0.dp
+ } else {
+ dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
+ }
+ ),
+ ) {
+ model.icon?.let { ChipIcon(viewModel = it, colors = model.colors) }
+
+ val isIconOnly = model is OngoingActivityChipModel.Shown.IconOnly
+ val isTextOnly = model.icon == null
+ if (!isIconOnly) {
+ ChipContent(
+ viewModel = model,
+ modifier =
+ Modifier.padding(
+ start =
+ if (isTextOnly || hasEmbeddedIcon) {
+ 0.dp
+ } else {
+ dimensionResource(
+ id = R.dimen.ongoing_activity_chip_icon_text_padding
+ )
+ },
+ end =
+ if (hasEmbeddedIcon) {
+ dimensionResource(
+ id =
+ R.dimen
+ .ongoing_activity_chip_text_end_padding_for_embedded_padding_icon
+ )
+ } else {
+ 0.dp
+ },
+ ),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ChipIcon(
+ viewModel: OngoingActivityChipModel.ChipIcon,
+ colors: ColorsModel,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+
+ when (viewModel) {
+ is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
+ val originalIcon = viewModel.impl
+ val iconSizePx =
+ context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_embedded_padding_icon_size
+ )
+ AndroidView(
+ modifier = modifier,
+ factory = { _ ->
+ originalIcon.apply {
+ layoutParams = ViewGroup.LayoutParams(iconSizePx, iconSizePx)
+ imageTintList = ColorStateList.valueOf(colors.text(context))
+ }
+ },
+ )
+ }
+
+ is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> {
+ Icon(
+ icon = viewModel.impl,
+ tint = Color(colors.text(context)),
+ modifier =
+ modifier.size(dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size)),
+ )
+ }
+
+ // TODO(b/372657935): Add recommended architecture implementation for
+ // StatusBarNotificationIcons
+ is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {}
+ }
+}
+
+@Composable
+private fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) {
+ val context = LocalContext.current
+ when (viewModel) {
+ is OngoingActivityChipModel.Shown.Timer -> {
+ ChronometerText(
+ startTimeMillis = viewModel.startTimeMs,
+ style = MaterialTheme.typography.labelLarge,
+ color = Color(viewModel.colors.text(context)),
+ modifier = modifier,
+ )
+ }
+
+ is OngoingActivityChipModel.Shown.Countdown -> {
+ ChipText(
+ text = viewModel.secondsUntilStarted.toString(),
+ color = Color(viewModel.colors.text(context)),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = modifier.neverDecreaseWidth(),
+ backgroundColor = Color(viewModel.colors.background(context).defaultColor),
+ )
+ }
+
+ is OngoingActivityChipModel.Shown.Text -> {
+ ChipText(
+ text = viewModel.text,
+ color = Color(viewModel.colors.text(context)),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = modifier,
+ backgroundColor = Color(viewModel.colors.background(context).defaultColor),
+ )
+ }
+
+ is OngoingActivityChipModel.Shown.ShortTimeDelta -> {
+ // TODO(b/372657935): Implement ShortTimeDelta content in compose.
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
new file mode 100644
index 000000000000..85ea087f531b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.compose
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+
+@Composable
+fun OngoingActivityChips(chips: MultipleOngoingActivityChipsModel, modifier: Modifier = Modifier) {
+ Row(
+ // TODO(b/372657935): Remove magic numbers for padding and spacing.
+ modifier = modifier.fillMaxHeight().padding(horizontal = 6.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ // TODO(b/372657935): Make sure chips are only shown when there is enough horizontal
+ // space.
+ if (chips.primary is OngoingActivityChipModel.Shown) {
+ OngoingActivityChip(model = chips.primary)
+ }
+ if (chips.secondary is OngoingActivityChipModel.Shown) {
+ OngoingActivityChip(model = chips.secondary)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index c81e8e211507..956d99e46766 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.chips.ui.model
import android.view.View
-import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -130,10 +129,6 @@ sealed class OngoingActivityChipModel {
*/
data class StatusBarView(val impl: StatusBarIconView) : ChipIcon {
init {
- check(Flags.statusBarCallChipNotificationIcon()) {
- "OngoingActivityChipModel.ChipIcon.StatusBarView created even though " +
- "Flags.statusBarCallChipNotificationIcon is not enabled"
- }
StatusBarConnectedDisplays.assertInLegacyMode()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
index ff3061e850d9..7b4b79d7c852 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
@@ -33,10 +33,8 @@ import androidx.annotation.UiThread
* that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to
* 1:00:00), but never smaller.
* 2) Hiding the text if the time gets too long for the space available. Once the text has been
- * hidden, it remains hidden for the duration of the activity.
- *
- * Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
- * text will also be hidden in landscape (even if there is enough space for it in landscape).
+ * hidden, it remains hidden for the duration of the activity (or until [resetWidthRestriction]
+ * is called).
*/
class ChipChronometer
@JvmOverloads
@@ -51,12 +49,23 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
private var shouldHideText: Boolean = false
override fun setBase(base: Long) {
- // These variables may have changed during the previous activity, so re-set them before the
- // new activity starts.
+ resetWidthRestriction()
+ super.setBase(base)
+ }
+
+ /**
+ * Resets any width restrictions that were placed on the chronometer.
+ *
+ * Should be used when the user's screen bounds changed because there may now be more room in
+ * the status bar to show additional content.
+ */
+ @UiThread
+ fun resetWidthRestriction() {
minimumTextWidth = 0
shouldHideText = false
+ // View needs to be visible in order to be re-measured
visibility = VISIBLE
- super.setBase(base)
+ forceLayout()
}
/** Sets whether this view should hide its text or not. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index eff959d0f83b..351cdc8e7f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.data.repository.LightBarControllerStore
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProviderImpl
+import com.android.systemui.statusbar.layout.ui.viewmodel.StatusBarContentInsetsViewModel
import com.android.systemui.statusbar.phone.AutoHideController
import com.android.systemui.statusbar.phone.AutoHideControllerImpl
import com.android.systemui.statusbar.phone.LightBarController
@@ -39,6 +40,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.ui.StatusBarUiLayerModule
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore
@@ -60,7 +62,14 @@ import dagger.multibindings.IntoMap
* ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule],
* [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
*/
-@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
+@Module(
+ includes =
+ [
+ StatusBarDataLayerModule::class,
+ StatusBarUiLayerModule::class,
+ SystemBarUtilsProxyImpl.Module::class,
+ ]
+)
interface StatusBarModule {
@Binds
@@ -169,5 +178,13 @@ interface StatusBarModule {
): StatusBarContentInsetsProvider {
return factory.create(context, configurationController, sysUICutoutProvider)
}
+
+ @Provides
+ @SysUISingleton
+ fun contentInsetsViewModel(
+ insetsProvider: StatusBarContentInsetsProvider
+ ): StatusBarContentInsetsViewModel {
+ return StatusBarContentInsetsViewModel(insetsProvider)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModel.kt
new file mode 100644
index 000000000000..03c07480ecea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.layout.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.layout.StatusBarContentInsetsChangedListener
+import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
+
+/** A recommended architecture version of [StatusBarContentInsetsProvider]. */
+class StatusBarContentInsetsViewModel(
+ private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider
+) {
+ /** Emits the status bar content area for the given rotation in absolute bounds. */
+ val contentArea: Flow<Rect> =
+ conflatedCallbackFlow {
+ val listener =
+ object : StatusBarContentInsetsChangedListener {
+ override fun onStatusBarContentInsetsChanged() {
+ trySend(
+ statusBarContentInsetsProvider
+ .getStatusBarContentAreaForCurrentRotation()
+ )
+ }
+ }
+ statusBarContentInsetsProvider.addCallback(listener)
+ awaitClose { statusBarContentInsetsProvider.removeCallback(listener) }
+ }
+ .onStart {
+ emit(statusBarContentInsetsProvider.getStatusBarContentAreaForCurrentRotation())
+ }
+ .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
new file mode 100644
index 000000000000..d2dccc49ffd7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.layout.ui.viewmodel
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.display.data.repository.SingleDisplayStore
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per-display instances of [StatusBarContentInsetsViewModel]. */
+interface StatusBarContentInsetsViewModelStore : PerDisplayStore<StatusBarContentInsetsViewModel>
+
+@SysUISingleton
+class MultiDisplayStatusBarContentInsetsViewModelStore
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+) :
+ StatusBarContentInsetsViewModelStore,
+ PerDisplayStoreImpl<StatusBarContentInsetsViewModel>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ override fun createInstanceForDisplay(displayId: Int): StatusBarContentInsetsViewModel? {
+ val insetsProvider =
+ statusBarContentInsetsProviderStore.forDisplay(displayId) ?: return null
+ return StatusBarContentInsetsViewModel(insetsProvider)
+ }
+
+ override val instanceClass = StatusBarContentInsetsViewModel::class.java
+}
+
+@SysUISingleton
+class SingleDisplayStatusBarContentInsetsViewModelStore
+@Inject
+constructor(statusBarContentInsetsViewModel: StatusBarContentInsetsViewModel) :
+ StatusBarContentInsetsViewModelStore,
+ PerDisplayStore<StatusBarContentInsetsViewModel> by SingleDisplayStore(
+ defaultInstance = statusBarContentInsetsViewModel
+ )
+
+@Module
+object StatusBarContentInsetsViewModelStoreModule {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarContentInsetsViewModelStore::class)
+ fun storeAsCoreStartable(
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarContentInsetsViewModelStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ return multiDisplayLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun store(
+ singleDisplayLazy: Lazy<SingleDisplayStatusBarContentInsetsViewModelStore>,
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarContentInsetsViewModelStore>,
+ ): StatusBarContentInsetsViewModelStore {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ singleDisplayLazy.get()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index c7535ec14d5d..eb5a3703bcfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -112,20 +112,20 @@ constructor(
if (StatusBarNotifChips.isEnabled) {
applicationScope.launch {
statusBarNotificationChipsInteractor.promotedNotificationChipTapEvent.collect {
- showPromotedNotificationHeadsUp(it)
+ onPromotedNotificationChipTapEvent(it)
}
}
}
}
/**
- * Shows the promoted notification with the given [key] as heads-up.
+ * Updates the heads-up state based on which promoted notification with the given [key] was
+ * tapped.
*
* Must be run on the main thread.
*/
- private fun showPromotedNotificationHeadsUp(key: String) {
+ private fun onPromotedNotificationChipTapEvent(key: String) {
StatusBarNotifChips.assertInNewMode()
- mLogger.logShowPromotedNotificationHeadsUp(key)
val entry = notifCollection.getEntry(key)
if (entry == null) {
@@ -135,22 +135,29 @@ constructor(
// TODO(b/364653005): Validate that the given key indeed matches a promoted notification,
// not just any notification.
+ val isCurrentlyHeadsUp = mHeadsUpManager.isHeadsUpEntry(entry.key)
val posted =
PostedEntry(
entry,
wasAdded = false,
wasUpdated = false,
- // Force-set this notification to show heads-up.
- shouldHeadsUpEver = true,
- shouldHeadsUpAgain = true,
+ // We want the chip to act as a toggle, so if the chip's notification is currently
+ // showing as heads up, then we should stop showing it.
+ shouldHeadsUpEver = !isCurrentlyHeadsUp,
+ shouldHeadsUpAgain = !isCurrentlyHeadsUp,
isPinnedByUser = true,
- isHeadsUpEntry = mHeadsUpManager.isHeadsUpEntry(entry.key),
+ isHeadsUpEntry = isCurrentlyHeadsUp,
isBinding = isEntryBinding(entry),
)
+ if (isCurrentlyHeadsUp) {
+ mLogger.logHidePromotedNotificationHeadsUp(key)
+ } else {
+ mLogger.logShowPromotedNotificationHeadsUp(key)
+ }
mExecutor.execute {
mPostedEntries[entry.key] = posted
- mNotifPromoter.invalidateList("showPromotedNotificationHeadsUp: ${entry.logKey}")
+ mNotifPromoter.invalidateList("onPromotedNotificationChipTapEvent: ${entry.logKey}")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index e443a0418ffd..5141aa35b041 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -148,6 +148,15 @@ class HeadsUpCoordinatorLogger(private val buffer: LogBuffer, private val verbos
)
}
+ fun logHidePromotedNotificationHeadsUp(key: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = key },
+ { "requesting promoted entry to hide heads up: $str1" },
+ )
+ }
+
fun logPromotedNotificationForHeadsUpNotFound(key: String) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 75c7d2d5be98..6140c92369b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -25,6 +25,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
+import com.android.systemui.statusbar.notification.domain.model.TopPinnedState
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import javax.inject.Inject
@@ -98,21 +99,39 @@ constructor(
}
}
- /** What [PinnedStatus] does the top row have? */
- private val topPinnedStatus: Flow<PinnedStatus> =
+ /** What [PinnedStatus] and key does the top row have? */
+ private val topPinnedState: Flow<TopPinnedState> =
headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
if (rows.isNotEmpty()) {
- combine(rows.map { it.pinnedStatus }) { pinnedStatus ->
- pinnedStatus.firstOrNull { it.isPinned } ?: PinnedStatus.NotPinned
+ // For each row, emits a (key, pinnedStatus) pair each time any row's
+ // `pinnedStatus` changes
+ val toCombine: List<Flow<Pair<String, PinnedStatus>>> =
+ rows.map { row -> row.pinnedStatus.map { status -> row.key to status } }
+ combine(toCombine) { pairs ->
+ val topPinnedRow: Pair<String, PinnedStatus>? =
+ pairs.firstOrNull { it.second.isPinned }
+ if (topPinnedRow != null) {
+ TopPinnedState.Pinned(
+ key = topPinnedRow.first,
+ status = topPinnedRow.second,
+ )
+ } else {
+ TopPinnedState.NothingPinned
+ }
}
} else {
- // if the set is empty, there are no flows to combine
- flowOf(PinnedStatus.NotPinned)
+ flowOf(TopPinnedState.NothingPinned)
}
}
/** Are there any pinned heads up rows to display? */
- val hasPinnedRows: Flow<Boolean> = topPinnedStatus.map { it.isPinned }
+ val hasPinnedRows: Flow<Boolean> =
+ topPinnedState.map {
+ when (it) {
+ is TopPinnedState.Pinned -> it.status.isPinned
+ is TopPinnedState.NothingPinned -> false
+ }
+ }
val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
@@ -142,13 +161,25 @@ constructor(
}
}
- /** Emits the pinned notification state as it relates to the status bar. */
- val statusBarHeadsUpState: Flow<PinnedStatus> =
- combine(topPinnedStatus, canShowHeadsUp) { topPinnedStatus, canShowHeadsUp ->
+ /**
+ * Emits the pinned notification state as it relates to the status bar. Includes both the pinned
+ * status and key of the notification that's pinned (if there is a pinned notification).
+ */
+ val statusBarHeadsUpState: Flow<TopPinnedState> =
+ combine(topPinnedState, canShowHeadsUp) { topPinnedState, canShowHeadsUp ->
if (canShowHeadsUp) {
- topPinnedStatus
+ topPinnedState
} else {
- PinnedStatus.NotPinned
+ TopPinnedState.NothingPinned
+ }
+ }
+
+ /** Emits the pinned notification status as it relates to the status bar. */
+ val statusBarHeadsUpStatus: Flow<PinnedStatus> =
+ statusBarHeadsUpState.map {
+ when (it) {
+ is TopPinnedState.Pinned -> it.status
+ is TopPinnedState.NothingPinned -> PinnedStatus.NotPinned
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 042389f7fde7..fd5973e0ab3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -25,7 +25,6 @@ import android.graphics.drawable.Icon
import android.service.notification.StatusBarNotification
import android.util.ArrayMap
import com.android.app.tracing.traceSection
-import com.android.systemui.Flags
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -132,12 +131,6 @@ private class ActiveNotificationsStoreBuilder(
}
private fun NotificationEntry.toModel(): ActiveNotificationModel {
- val statusBarChipIcon =
- if (Flags.statusBarCallChipNotificationIcon()) {
- icons.statusBarChipIcon
- } else {
- null
- }
val promotedContent =
if (PromotedNotificationContentModel.featureFlagEnabled()) {
promotedNotificationContentModel
@@ -158,7 +151,7 @@ private class ActiveNotificationsStoreBuilder(
aodIcon = icons.aodIcon?.sourceIcon,
shelfIcon = icons.shelfIcon?.sourceIcon,
statusBarIcon = icons.statusBarIcon?.sourceIcon,
- statusBarChipIconView = statusBarChipIcon,
+ statusBarChipIconView = icons.statusBarChipIcon,
uid = sbn.uid,
packageName = sbn.packageName,
contentIntent = sbn.notification.contentIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/model/TopPinnedState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/model/TopPinnedState.kt
new file mode 100644
index 000000000000..51c448adf998
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/model/TopPinnedState.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.model
+
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
+
+/** A class representing the state of the top pinned row. */
+sealed interface TopPinnedState {
+ /** Nothing is pinned. */
+ data object NothingPinned : TopPinnedState
+
+ /**
+ * The top pinned row is a notification with the given key and status.
+ *
+ * @property status must have [PinnedStatus.isPinned] as true.
+ */
+ data class Pinned(val key: String, val status: PinnedStatus) : TopPinnedState {
+ init {
+ check(status.isPinned)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index b56a838a80a5..31375cc4a03a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -162,9 +162,7 @@ constructor(
val sbIcon = iconBuilder.createIconView(entry)
sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
val sbChipIcon: StatusBarIconView?
- if (
- Flags.statusBarCallChipNotificationIcon() && !StatusBarConnectedDisplays.isEnabled
- ) {
+ if (!StatusBarConnectedDisplays.isEnabled) {
sbChipIcon = iconBuilder.createIconView(entry)
sbChipIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
} else {
@@ -186,7 +184,7 @@ constructor(
try {
setIcon(entry, normalIconDescriptor, sbIcon)
- if (Flags.statusBarCallChipNotificationIcon() && sbChipIcon != null) {
+ if (sbChipIcon != null) {
setIcon(entry, normalIconDescriptor, sbChipIcon)
}
setIcon(entry, sensitiveIconDescriptor, shelfIcon)
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/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
index 10e67a40ebc9..640d364895ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -25,7 +25,7 @@ import android.app.NotificationManager.IMPORTANCE_NONE
import android.app.NotificationManager.IMPORTANCE_UNSPECIFIED
import android.content.Context
import android.graphics.drawable.Drawable
-import android.text.TextUtils
+import android.text.TextUtils.isEmpty
import android.transition.AutoTransition
import android.transition.Transition
import android.transition.TransitionManager
@@ -37,13 +37,10 @@ import android.widget.LinearLayout
import android.widget.Switch
import android.widget.TextView
import com.android.settingslib.Utils
-
import com.android.systemui.res.R
import com.android.systemui.util.Assert
-/**
- * Half-shelf for notification channel controls
- */
+/** Half-shelf for notification channel controls */
class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
lateinit var controller: ChannelEditorDialogController
var appIcon: Drawable? = null
@@ -84,23 +81,21 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a
val transition = AutoTransition()
transition.duration = 200
- transition.addListener(object : Transition.TransitionListener {
- override fun onTransitionEnd(p0: Transition?) {
- notifySubtreeAccessibilityStateChangedIfNeeded()
- }
+ transition.addListener(
+ object : Transition.TransitionListener {
+ override fun onTransitionEnd(p0: Transition?) {
+ notifySubtreeAccessibilityStateChangedIfNeeded()
+ }
- override fun onTransitionResume(p0: Transition?) {
- }
+ override fun onTransitionResume(p0: Transition?) {}
- override fun onTransitionPause(p0: Transition?) {
- }
+ override fun onTransitionPause(p0: Transition?) {}
- override fun onTransitionCancel(p0: Transition?) {
- }
+ override fun onTransitionCancel(p0: Transition?) {}
- override fun onTransitionStart(p0: Transition?) {
+ override fun onTransitionStart(p0: Transition?) {}
}
- })
+ )
TransitionManager.beginDelayedTransition(this, transition)
// Remove any rows
@@ -130,8 +125,9 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a
private fun updateAppControlRow(enabled: Boolean) {
appControlRow.iconView.setImageDrawable(appIcon)
- appControlRow.channelName.text = context.resources
- .getString(R.string.notification_channel_dialog_title, appName)
+ val title = context.resources.getString(R.string.notification_channel_dialog_title, appName)
+ appControlRow.channelName.text = title
+ appControlRow.switch.contentDescription = title
appControlRow.switch.isChecked = enabled
appControlRow.switch.setOnCheckedChangeListener { _, b ->
controller.proposeSetAppNotificationsEnabled(b)
@@ -164,8 +160,8 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
var gentle = false
init {
- highlightColor = Utils.getColorAttrDefaultColor(
- context, android.R.attr.colorControlHighlight)
+ highlightColor =
+ Utils.getColorAttrDefaultColor(context, android.R.attr.colorControlHighlight)
}
var channel: NotificationChannel? = null
@@ -182,17 +178,16 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
switch = requireViewById(R.id.toggle)
switch.setOnCheckedChangeListener { _, b ->
channel?.let {
- controller.proposeEditForChannel(it,
- if (b) it.originalImportance.coerceAtLeast(IMPORTANCE_LOW)
- else IMPORTANCE_NONE)
+ controller.proposeEditForChannel(
+ it,
+ if (b) it.originalImportance.coerceAtLeast(IMPORTANCE_LOW) else IMPORTANCE_NONE,
+ )
}
}
setOnClickListener { switch.toggle() }
}
- /**
- * Play an animation that highlights this row
- */
+ /** Play an animation that highlights this row */
fun playHighlight() {
// Use 0 for the start value because our background is given to us by our parent
val fadeInLoop = ValueAnimator.ofObject(ArgbEvaluator(), 0, highlightColor)
@@ -211,17 +206,21 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
channelName.text = nc.name ?: ""
- nc.group?.let { groupId ->
- channelDescription.text = controller.groupNameForId(groupId)
- }
+ nc.group?.let { groupId -> channelDescription.text = controller.groupNameForId(groupId) }
- if (nc.group == null || TextUtils.isEmpty(channelDescription.text)) {
+ if (nc.group == null || isEmpty(channelDescription.text)) {
channelDescription.visibility = View.GONE
} else {
channelDescription.visibility = View.VISIBLE
}
switch.isChecked = nc.importance != IMPORTANCE_NONE
+ switch.contentDescription =
+ if (isEmpty(channelDescription.text)) {
+ channelName.text
+ } else {
+ "${channelName.text} ${channelDescription.text}"
+ }
}
private fun updateImportance() {
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 3866f79ed701..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 {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
new file mode 100644
index 000000000000..e27ff7d6746b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+
+/**
+ * A background style for smarter-smart-actions.
+ *
+ * TODO(b/383567383) implement final UX
+ */
+class MagicActionBackgroundDrawable(context: Context) : Drawable() {
+
+ private var _alpha: Int = 255
+ private var _colorFilter: ColorFilter? = null
+ private val paint =
+ Paint().apply {
+ color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ }
+
+ override fun draw(canvas: Canvas) {
+ canvas.drawRect(bounds, paint)
+ }
+
+ override fun setAlpha(alpha: Int) {
+ _alpha = alpha
+ invalidateSelf()
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ _colorFilter = colorFilter
+ invalidateSelf()
+ }
+
+ override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 7c44eae6c0b8..70e27a981b49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static android.app.Flags.notificationsRedesignTemplates;
-
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
@@ -481,16 +479,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
logger.logAsyncTaskProgress(entryForLogging,
"creating low-priority group summary remote view");
result.mNewMinimizedGroupHeaderView =
- builder.makeLowPriorityContentView(/* useRegularSubtext = */ true,
- /* highlightExpander = */ notificationsRedesignTemplates());
+ builder.makeLowPriorityContentView(true /* useRegularSubtext */);
}
}
setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
result.packageContext = packageContext;
result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
- /* showingPublic = */ false);
+ false /* showingPublic */);
result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
- /* showingPublic = */ true);
+ true /* showingPublic */);
return result;
});
@@ -1139,8 +1136,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private static RemoteViews createContentView(Notification.Builder builder,
boolean isMinimized, boolean useLarge) {
if (isMinimized) {
- return builder.makeLowPriorityContentView(/* useRegularSubtext = */ false,
- /* highlightExpander = */ false);
+ return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
}
return builder.createContentView(useLarge);
}
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/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index ae9b69c8f6bf..c619b17f1ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.row
import android.annotation.SuppressLint
-import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
import android.app.Notification.MessagingStyle
import android.content.Context
@@ -888,10 +887,7 @@ constructor(
entryForLogging,
"creating low-priority group summary remote view",
)
- builder.makeLowPriorityContentView(
- /* useRegularSubtext = */ true,
- /* highlightExpander = */ notificationsRedesignTemplates(),
- )
+ builder.makeLowPriorityContentView(true /* useRegularSubtext */)
} else null
NewRemoteViews(
contracted = contracted,
@@ -1661,10 +1657,7 @@ constructor(
useLarge: Boolean,
): RemoteViews {
return if (isMinimized) {
- builder.makeLowPriorityContentView(
- /* useRegularSubtext = */ false,
- /* highlightExpander = */ false,
- )
+ builder.makeLowPriorityContentView(false /* useRegularSubtext */)
} else builder.createContentView(useLarge)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index e477c7430262..8e48065d9d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -568,8 +568,7 @@ public class NotificationChildrenContainer extends ViewGroup
builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
}
- header = builder.makeLowPriorityContentView(true /* useRegularSubtext */,
- notificationsRedesignTemplates() /* highlightExpander */);
+ header = builder.makeLowPriorityContentView(true /* useRegularSubtext */);
if (mMinimizedGroupHeader == null) {
mMinimizedGroupHeader = (NotificationHeaderView) header.apply(getContext(),
this);
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 e752e6581421..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;
@@ -1237,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),
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/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6f29f618ee0d..afc5bc67c0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -750,7 +750,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
);
} else if (biometricSourceType == BiometricSourceType.FINGERPRINT
- && mUpdateMonitor.isUdfpsSupported()) {
+ && mUpdateMonitor.isOpticalUdfpsSupported()) {
long currUptimeMillis = mSystemClock.uptimeMillis();
if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) {
mNumConsecutiveFpFailures += 1;
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/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index c57cede754d3..f56c2d5dc5e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -18,8 +18,6 @@ package com.android.systemui.statusbar.phone.ongoingcall
import android.app.ActivityManager
import android.app.IActivityManager
-import android.app.Notification
-import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
import android.app.PendingIntent
import android.app.UidObserver
import android.content.Context
@@ -44,9 +42,6 @@ import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
@@ -60,7 +55,9 @@ import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-/** A controller to handle the ongoing call chip in the collapsed status bar.
+/**
+ * A controller to handle the ongoing call chip in the collapsed status bar.
+ *
* @deprecated Use [OngoingCallInteractor] instead, which follows recommended architecture patterns
*/
@Deprecated("Use OngoingCallInteractor instead")
@@ -71,7 +68,6 @@ constructor(
@Application private val scope: CoroutineScope,
private val context: Context,
private val ongoingCallRepository: OngoingCallRepository,
- private val notifCollection: CommonNotifCollection,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
private val systemClock: SystemClock,
private val activityStarter: ActivityStarter,
@@ -90,105 +86,24 @@ constructor(
private val mListeners: MutableList<OngoingCallListener> = mutableListOf()
private val uidObserver = CallAppUidObserver()
- private val notifListener =
- object : NotifCollectionListener {
- // Temporary workaround for b/178406514 for testing purposes.
- //
- // b/178406514 means that posting an incoming call notif then updating it to an ongoing
- // call notif does not work (SysUI never receives the update). This workaround allows us
- // to trigger the ongoing call chip when an ongoing call notif is *added* rather than
- // *updated*, allowing us to test the chip.
- //
- // TODO(b/183229367): Remove this function override when b/178406514 is fixed.
- override fun onEntryAdded(entry: NotificationEntry) {
- onEntryUpdated(entry, true)
- }
-
- override fun onEntryUpdated(entry: NotificationEntry) {
- StatusBarUseReposForCallChip.assertInLegacyMode()
- // We have a new call notification or our existing call notification has been
- // updated.
- // TODO(b/183229367): This likely won't work if you take a call from one app then
- // switch to a call from another app.
- if (
- callNotificationInfo == null && isCallNotification(entry) ||
- (entry.sbn.key == callNotificationInfo?.key)
- ) {
- val newOngoingCallInfo =
- CallNotificationInfo(
- entry.sbn.key,
- entry.sbn.notification.getWhen(),
- // In this old listener pattern, we don't have access to the
- // notification icon.
- notificationIconView = null,
- entry.sbn.notification.contentIntent,
- entry.sbn.uid,
- entry.sbn.notification.extras.getInt(
- Notification.EXTRA_CALL_TYPE,
- -1,
- ) == CALL_TYPE_ONGOING,
- statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
- )
- if (newOngoingCallInfo == callNotificationInfo) {
- return
- }
-
- callNotificationInfo = newOngoingCallInfo
- if (newOngoingCallInfo.isOngoing) {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = newOngoingCallInfo.key },
- { "Call notif *is* ongoing -> showing chip. key=$str1" },
- )
- updateChip()
- } else {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = newOngoingCallInfo.key },
- { "Call notif not ongoing -> hiding chip. key=$str1" },
- )
- removeChip()
- }
- }
- }
-
- override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (entry.sbn.key == callNotificationInfo?.key) {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = entry.sbn.key },
- { "Call notif removed -> hiding chip. key=$str1" },
- )
- removeChip()
- }
- }
- }
override fun start() {
- if (StatusBarChipsModernization.isEnabled)
- return
+ if (StatusBarChipsModernization.isEnabled) return
dumpManager.registerDumpable(this)
- if (Flags.statusBarUseReposForCallChip()) {
- scope.launch {
- // Listening to [ActiveNotificationsInteractor] instead of using
- // [NotifCollectionListener#onEntryUpdated] is better for two reasons:
- // 1. ActiveNotificationsInteractor automatically filters the notification list to
- // just notifications for the current user, which ensures we don't show a call chip
- // for User 1's call while User 2 is active (see b/328584859).
- // 2. ActiveNotificationsInteractor only emits notifications that are currently
- // present in the shade, which means we know we've already inflated the icon that we
- // might use for the call chip (see b/354930838).
- activeNotificationsInteractor.ongoingCallNotification.collect {
- updateInfoFromNotifModel(it)
- }
+ scope.launch {
+ // Listening to [ActiveNotificationsInteractor] instead of using
+ // [NotifCollectionListener#onEntryUpdated] is better for two reasons:
+ // 1. ActiveNotificationsInteractor automatically filters the notification list to
+ // just notifications for the current user, which ensures we don't show a call chip
+ // for User 1's call while User 2 is active (see b/328584859).
+ // 2. ActiveNotificationsInteractor only emits notifications that are currently
+ // present in the shade, which means we know we've already inflated the icon that we
+ // might use for the call chip (see b/354930838).
+ activeNotificationsInteractor.ongoingCallNotification.collect {
+ updateInfoFromNotifModel(it)
}
- } else {
- notifCollection.addCollectionListener(notifListener)
}
scope.launch {
@@ -244,21 +159,12 @@ constructor(
logger.log(
TAG,
LogLevel.DEBUG,
- {
- bool1 = Flags.statusBarCallChipNotificationIcon()
- bool2 = currentInfo.notificationIconView != null
- },
- { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" },
+ { bool1 = currentInfo.notificationIconView != null },
+ { "Creating OngoingCallModel.InCall. hasIcon=$bool1" },
)
- val icon =
- if (Flags.statusBarCallChipNotificationIcon()) {
- currentInfo.notificationIconView
- } else {
- null
- }
return OngoingCallModel.InCall(
startTimeMs = currentInfo.callStartTime,
- notificationIconView = icon,
+ notificationIconView = currentInfo.notificationIconView,
intent = currentInfo.intent,
notificationKey = currentInfo.key,
)
@@ -597,8 +503,4 @@ constructor(
}
}
-private fun isCallNotification(entry: NotificationEntry): Boolean {
- return entry.sbn.notification.isStyle(Notification.CallStyle::class.java)
-}
-
private const val TAG = OngoingCallRepository.TAG
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt
deleted file mode 100644
index 4bdd90ebff3e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.phone.ongoingcall
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the status bar use repos for call chip flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object StatusBarUseReposForCallChip {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.statusBarUseReposForCallChip()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
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..31d6d86d1b37 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
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernizat
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
/**
@@ -107,10 +108,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)
}
}
@@ -121,22 +121,26 @@ constructor(
!StatusBarNotifChips.isEnabled &&
!StatusBarChipsModernization.isEnabled
) {
- val primaryChipView: View =
- view.requireViewById(R.id.ongoing_activity_chip_primary)
+ val primaryChipViewBinding =
+ OngoingActivityChipBinder.createBinding(
+ view.requireViewById(R.id.ongoing_activity_chip_primary)
+ )
launch {
viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
OngoingActivityChipBinder.bind(
primaryChipModel,
- primaryChipView,
+ primaryChipViewBinding,
iconViewStore,
)
if (StatusBarRootModernization.isEnabled) {
when (primaryChipModel) {
is OngoingActivityChipModel.Shown ->
- primaryChipView.show(shouldAnimateChange = true)
+ primaryChipViewBinding.rootView.show(
+ shouldAnimateChange = true
+ )
is OngoingActivityChipModel.Hidden ->
- primaryChipView.hide(
+ primaryChipViewBinding.rootView.hide(
state = View.GONE,
shouldAnimateChange = primaryChipModel.shouldAnimate,
)
@@ -167,28 +171,34 @@ constructor(
StatusBarNotifChips.isEnabled &&
!StatusBarChipsModernization.isEnabled
) {
- val primaryChipView: View =
- view.requireViewById(R.id.ongoing_activity_chip_primary)
- val secondaryChipView: View =
- view.requireViewById(R.id.ongoing_activity_chip_secondary)
+ // Create view bindings here so we don't keep re-fetching child views each time
+ // the chip model changes.
+ val primaryChipViewBinding =
+ OngoingActivityChipBinder.createBinding(
+ view.requireViewById(R.id.ongoing_activity_chip_primary)
+ )
+ val secondaryChipViewBinding =
+ OngoingActivityChipBinder.createBinding(
+ view.requireViewById(R.id.ongoing_activity_chip_secondary)
+ )
launch {
- viewModel.ongoingActivityChips.collect { chips ->
+ viewModel.ongoingActivityChips.collectLatest { chips ->
OngoingActivityChipBinder.bind(
chips.primary,
- primaryChipView,
+ primaryChipViewBinding,
iconViewStore,
)
- // TODO(b/364653005): Don't show the secondary chip if there isn't
- // enough space for it.
OngoingActivityChipBinder.bind(
chips.secondary,
- secondaryChipView,
+ secondaryChipViewBinding,
iconViewStore,
)
if (StatusBarRootModernization.isEnabled) {
- primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
- secondaryChipView.adjustVisibility(
+ primaryChipViewBinding.rootView.adjustVisibility(
+ chips.primary.toVisibilityModel()
+ )
+ secondaryChipViewBinding.rootView.adjustVisibility(
chips.secondary.toVisibilityModel()
)
} else {
@@ -201,6 +211,18 @@ constructor(
shouldAnimate = true,
)
}
+
+ viewModel.contentArea.collect { _ ->
+ OngoingActivityChipBinder.resetPrimaryChipWidthRestrictions(
+ primaryChipViewBinding,
+ viewModel.ongoingActivityChips.value.primary,
+ )
+ OngoingActivityChipBinder.resetSecondaryChipWidthRestrictions(
+ secondaryChipViewBinding,
+ viewModel.ongoingActivityChips.value.secondary,
+ )
+ view.requestLayout()
+ }
}
}
}
@@ -218,7 +240,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..71e19188f309 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
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -33,9 +34,11 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.compose.theme.PlatformTheme
import com.android.keyguard.AlphaOptimizedLinearLayout
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
@@ -53,13 +56,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 +74,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,
@@ -158,6 +163,45 @@ fun StatusBarRoot(
darkIconDispatcher,
)
iconController.addIconGroup(darkIconManager)
+
+ if (StatusBarChipsModernization.isEnabled) {
+ val startSideExceptHeadsUp =
+ phoneStatusBarView.requireViewById<LinearLayout>(
+ R.id.status_bar_start_side_except_heads_up
+ )
+
+ val composeView =
+ ComposeView(context).apply {
+ layoutParams =
+ LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ )
+
+ setViewCompositionStrategy(
+ ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
+ )
+
+ setContent {
+ PlatformTheme {
+ val chips by
+ statusBarViewModel.ongoingActivityChips
+ .collectAsStateWithLifecycle()
+ OngoingActivityChips(chips = chips)
+ }
+ }
+ }
+
+ // Add the composable container for ongoingActivityChips before the
+ // notification_icon_area to maintain the same ordering for ongoing activity
+ // chips in the status bar layout.
+ val notificationIconAreaIndex =
+ startSideExceptHeadsUp.indexOfChild(
+ startSideExceptHeadsUp.findViewById(R.id.notification_icon_area)
+ )
+ startSideExceptHeadsUp.addView(composeView, notificationIconAreaIndex)
+ }
+
HomeStatusBarIconBlockListBinder.bind(
statusIconContainer,
darkIconManager,
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..d9d9a29ee2b6 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
@@ -44,6 +43,7 @@ import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationSt
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel
+import com.android.systemui.statusbar.layout.ui.viewmodel.StatusBarContentInsetsViewModelStore
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
@@ -53,7 +53,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 +120,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
@@ -128,6 +131,9 @@ interface HomeStatusBarViewModel {
/** Which icons to block from the home status bar */
val iconBlockList: Flow<List<String>>
+ /** This status bar's current content area for the given rotation in absolute bounds. */
+ val contentArea: Flow<Rect>
+
/**
* Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
* status bar and navigation icons dim. In this mode, a notification dot appears where the
@@ -137,13 +143,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 +163,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,
@@ -178,6 +189,7 @@ constructor(
ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel,
animations: SystemStatusEventAnimationInteractor,
+ statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Application coroutineScope: CoroutineScope,
) : HomeStatusBarViewModel {
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
@@ -211,22 +223,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)) {
@@ -283,11 +295,12 @@ constructor(
override val shouldShowOperatorNameView: Flow<Boolean> =
combine(
shouldHomeStatusBarBeVisible,
- headsUpNotificationInteractor.statusBarHeadsUpState,
+ headsUpNotificationInteractor.statusBarHeadsUpStatus,
homeStatusBarInteractor.visibilityViaDisableFlags,
homeStatusBarInteractor.shouldShowOperatorName,
- ) { shouldStatusBarBeVisible, headsUpState, visibilityViaDisableFlags, shouldShowOperator ->
- val hideForHeadsUp = headsUpState == PinnedStatus.PinnedBySystem
+ ) { shouldStatusBarBeVisible, headsUpStatus, visibilityViaDisableFlags, shouldShowOperator
+ ->
+ val hideForHeadsUp = headsUpStatus == PinnedStatus.PinnedBySystem
shouldStatusBarBeVisible &&
!hideForHeadsUp &&
visibilityViaDisableFlags.isSystemInfoAllowed &&
@@ -297,10 +310,10 @@ constructor(
override val isClockVisible: Flow<VisibilityModel> =
combine(
shouldHomeStatusBarBeVisible,
- headsUpNotificationInteractor.statusBarHeadsUpState,
+ headsUpNotificationInteractor.statusBarHeadsUpStatus,
homeStatusBarInteractor.visibilityViaDisableFlags,
- ) { shouldStatusBarBeVisible, headsUpState, visibilityViaDisableFlags ->
- val hideClockForHeadsUp = headsUpState == PinnedStatus.PinnedBySystem
+ ) { shouldStatusBarBeVisible, headsUpStatus, visibilityViaDisableFlags ->
+ val hideClockForHeadsUp = headsUpStatus == PinnedStatus.PinnedBySystem
val showClock =
shouldStatusBarBeVisible &&
visibilityViaDisableFlags.isClockAllowed &&
@@ -356,6 +369,10 @@ constructor(
override val iconBlockList: Flow<List<String>> =
homeStatusBarIconBlockListInteractor.iconBlockList
+ override val contentArea: Flow<Rect> =
+ statusBarContentInsetsViewModelStore.forDisplay(thisDisplayId)?.contentArea
+ ?: flowOf(Rect(0, 0, 0, 0))
+
@View.Visibility
private fun Boolean.toVisibleOrGone(): Int {
return if (this) View.VISIBLE else View.GONE
@@ -364,6 +381,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/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index 56c9e9abbc36..cb26679434ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -41,6 +41,7 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.Button
+import com.android.systemui.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.shared.system.ActivityManagerWrapper
@@ -52,6 +53,7 @@ import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.row.MagicActionBackgroundDrawable
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
@@ -400,6 +402,15 @@ constructor(
.apply {
text = action.title
+ if (Flags.notificationMagicActionsTreatment()) {
+ if (
+ smartActions.fromAssistant &&
+ action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false)
+ ) {
+ background = MagicActionBackgroundDrawable(parent.context)
+ }
+ }
+
// We received the Icon from the application - so use the Context of the application
// to
// reference icon resources.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/StatusBarUiLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/StatusBarUiLayerModule.kt
new file mode 100644
index 000000000000..8e81d78d60f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/StatusBarUiLayerModule.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.ui
+
+import com.android.systemui.statusbar.layout.ui.viewmodel.StatusBarContentInsetsViewModelStoreModule
+import dagger.Module
+
+@Module(includes = [StatusBarContentInsetsViewModelStoreModule::class])
+object StatusBarUiLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 6175ea190697..a98a9e0c16d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -60,7 +60,7 @@ constructor(
private val showingHeadsUpStatusBar: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
- headsUpNotificationInteractor.statusBarHeadsUpState.map { it.isPinned }
+ headsUpNotificationInteractor.statusBarHeadsUpStatus.map { it.isPinned }
} else {
flowOf(false)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 4abbbacd800b..bac2c47f51c7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -24,13 +24,15 @@ import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE
+import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
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
@@ -97,8 +99,9 @@ import org.mockito.kotlin.clearInvocations
class ClockEventControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val zenModeRepository = kosmos.fakeZenModeRepository
private val testScope = kosmos.testScope
+ private val zenModeRepository by lazy { kosmos.fakeZenModeRepository }
+ private val zenModeInteractor by lazy { kosmos.zenModeInteractor }
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -106,7 +109,6 @@ class ClockEventControllerTest : SysuiTestCase() {
private lateinit var repository: FakeKeyguardRepository
private val clockBuffers = ClockMessageBuffers(LogcatOnlyMessageBuffer(LogLevel.DEBUG))
private lateinit var underTest: ClockEventController
- private lateinit var dndModeId: String
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var batteryController: BatteryController
@@ -156,17 +158,12 @@ class ClockEventControllerTest : SysuiTestCase() {
whenever(largeClockController.theme).thenReturn(ThemeConfig(true, null))
whenever(userTracker.userId).thenReturn(1)
- dndModeId = MANUAL_DND_INACTIVE.id
- zenModeRepository.addMode(MANUAL_DND_INACTIVE)
+ repository = kosmos.fakeKeyguardRepository
- repository = 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,9 +174,9 @@ class ClockEventControllerTest : SysuiTestCase() {
mainExecutor,
bgExecutor,
clockBuffers,
- withDeps.featureFlags,
+ kosmos.fakeFeatureFlagsClassic,
zenModeController,
- kosmos.zenModeInteractor,
+ zenModeInteractor,
userTracker,
)
underTest.clock = clock
@@ -504,7 +501,7 @@ class ClockEventControllerTest : SysuiTestCase() {
runCurrent()
clearInvocations(events)
- zenModeRepository.activateMode(dndModeId)
+ zenModeRepository.activateMode(MANUAL_DND)
runCurrent()
verify(events)
@@ -512,7 +509,7 @@ class ClockEventControllerTest : SysuiTestCase() {
eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
)
- zenModeRepository.deactivateMode(dndModeId)
+ zenModeRepository.deactivateMode(MANUAL_DND)
runCurrent()
verify(events).onZenDataChanged(eq(ZenData(ZenMode.OFF, R.string::dnd_is_off.name)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index 5d622eaeb1aa..e61acc4e1d0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -224,6 +225,30 @@ class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun testOnActionIconClick_audioSharingMediaDevice_stopBroadcast() {
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ actionInteractorImpl.onActionIconClick(inAudioSharingMediaDeviceItem) {}
+ assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
+ .isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun testOnActionIconClick_availableAudioSharingMediaDevice_startBroadcast() {
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ actionInteractorImpl.onActionIconClick(connectedAudioSharingMediaDeviceItem) {}
+ assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
+ .isEqualTo(true)
+ }
+ }
+ }
+
private companion object {
const val DEVICE_NAME = "device"
const val DEVICE_CONNECTION_SUMMARY = "active"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 6bfd08025833..4396b0a42ae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -32,6 +32,9 @@ import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.model.SysUiState
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
@@ -43,9 +46,8 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
@@ -93,7 +95,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
private val fakeSystemClock = FakeSystemClock()
- private lateinit var scheduler: TestCoroutineScheduler
private lateinit var dispatcher: CoroutineDispatcher
private lateinit var testScope: TestScope
private lateinit var icon: Pair<Drawable, String>
@@ -104,9 +105,8 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
@Before
fun setUp() {
- scheduler = TestCoroutineScheduler()
- dispatcher = UnconfinedTestDispatcher(scheduler)
- testScope = TestScope(dispatcher)
+ dispatcher = kosmos.testDispatcher
+ testScope = kosmos.testScope
whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
@@ -124,23 +124,19 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
kosmos.shadeDialogContextInteractor,
)
- whenever(
- sysuiDialogFactory.create(
- any(SystemUIDialog.Delegate::class.java),
- any()
- )
- ).thenAnswer {
- SystemUIDialog(
- mContext,
- 0,
- SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
- dialogManager,
- sysuiState,
- fakeBroadcastDispatcher,
- dialogTransitionAnimator,
- it.getArgument(0),
- )
- }
+ whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java), any()))
+ .thenAnswer {
+ SystemUIDialog(
+ mContext,
+ 0,
+ SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogTransitionAnimator,
+ it.getArgument(0),
+ )
+ }
icon = Pair(drawable, DEVICE_NAME)
deviceItem =
@@ -194,20 +190,29 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
@Test
fun testDeviceItemViewHolder_cachedDeviceNotBusy() {
- deviceItem.isEnabled = true
-
- val view =
- LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
- val viewHolder =
- mBluetoothTileDialogDelegate
- .Adapter(bluetoothTileDialogCallback)
- .DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
- val container = view.requireViewById<View>(R.id.bluetooth_device_row)
-
- assertThat(container).isNotNull()
- assertThat(container.isEnabled).isTrue()
- assertThat(container.hasOnClickListeners()).isTrue()
+ testScope.runTest {
+ deviceItem.isEnabled = true
+
+ val view =
+ LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
+ val viewHolder = mBluetoothTileDialogDelegate.Adapter().DeviceItemViewHolder(view)
+ viewHolder.bind(deviceItem)
+ val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+
+ assertThat(container).isNotNull()
+ assertThat(container.isEnabled).isTrue()
+ assertThat(container.hasOnClickListeners()).isTrue()
+ val value by collectLastValue(mBluetoothTileDialogDelegate.deviceItemClick)
+ runCurrent()
+ container.performClick()
+ runCurrent()
+ assertThat(value).isNotNull()
+ value?.let {
+ assertThat(it.target).isEqualTo(DeviceItemClick.Target.ENTIRE_ROW)
+ assertThat(it.clickedView).isEqualTo(container)
+ assertThat(it.deviceItem).isEqualTo(deviceItem)
+ }
+ }
}
@Test
@@ -229,9 +234,9 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
sysuiDialogFactory,
kosmos.shadeDialogContextInteractor,
)
- .Adapter(bluetoothTileDialogCallback)
+ .Adapter()
.DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
+ viewHolder.bind(deviceItem)
val container = view.requireViewById<View>(R.id.bluetooth_device_row)
assertThat(container).isNotNull()
@@ -240,6 +245,32 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
}
@Test
+ fun testDeviceItemViewHolder_clickActionIcon() {
+ testScope.runTest {
+ deviceItem.isEnabled = true
+
+ val view =
+ LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
+ val viewHolder = mBluetoothTileDialogDelegate.Adapter().DeviceItemViewHolder(view)
+ viewHolder.bind(deviceItem)
+ val actionIconView = view.requireViewById<View>(R.id.gear_icon)
+
+ assertThat(actionIconView).isNotNull()
+ assertThat(actionIconView.hasOnClickListeners()).isTrue()
+ val value by collectLastValue(mBluetoothTileDialogDelegate.deviceItemClick)
+ runCurrent()
+ actionIconView.performClick()
+ runCurrent()
+ assertThat(value).isNotNull()
+ value?.let {
+ assertThat(it.target).isEqualTo(DeviceItemClick.Target.ACTION_ICON)
+ assertThat(it.clickedView).isEqualTo(actionIconView)
+ assertThat(it.deviceItem).isEqualTo(deviceItem)
+ }
+ }
+ }
+
+ @Test
fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
testScope.runTest {
val dialog = mBluetoothTileDialogDelegate.createDialog()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 5bf15137b834..0aa5199cb20e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -118,6 +118,7 @@ class DeviceItemFactoryTest : SysuiTestCase() {
.isEqualTo(DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
assertThat(deviceItem.cachedBluetoothDevice).isEqualTo(cachedDevice)
assertThat(deviceItem.deviceName).isEqualTo(DEVICE_NAME)
+ assertThat(deviceItem.actionIconRes).isEqualTo(R.drawable.ic_add)
assertThat(deviceItem.isActive).isFalse()
assertThat(deviceItem.connectionSummary)
.isEqualTo(
@@ -292,6 +293,7 @@ class DeviceItemFactoryTest : SysuiTestCase() {
assertThat(deviceItem.cachedBluetoothDevice).isEqualTo(cachedDevice)
assertThat(deviceItem.deviceName).isEqualTo(DEVICE_NAME)
assertThat(deviceItem.connectionSummary).isEqualTo(CONNECTION_SUMMARY)
+ assertThat(deviceItem.actionIconRes).isEqualTo(R.drawable.ic_settings_24dp)
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index 57a12df0cfee..c4ef4f978ff8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -35,7 +35,6 @@ import android.platform.test.annotations.EnableFlags
import androidx.test.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -112,20 +111,8 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun testCreateIcons_chipNotifIconFlagDisabled_statusBarChipIconIsNull() {
- val entry =
- notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
- entry?.let { iconManager.createIcons(it) }
- testScope.runCurrent()
-
- assertThat(entry?.icons?.statusBarChipIcon).isNull()
- }
-
- @Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun testCreateIcons_chipNotifIconFlagEnabled_cdFlagDisabled_statusBarChipIconIsNotNull() {
+ fun testCreateIcons_cdFlagDisabled_statusBarChipIconIsNotNull() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.let { iconManager.createIcons(it) }
@@ -135,8 +122,8 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
- fun testCreateIcons_chipNotifIconFlagEnabled_cdFlagEnabled_statusBarChipIconIsNull() {
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun testCreateIcons_cdFlagEnabled_statusBarChipIconIsNull() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.let { iconManager.createIcons(it) }
@@ -217,7 +204,6 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun testCreateIcons_cdFlagDisabled_sensitiveImportantConversation() {
val entry =
@@ -233,7 +219,7 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun testCreateIcons_cdFlagEnabled_sensitiveImportantConversation() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
@@ -248,7 +234,6 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun testUpdateIcons_cdFlagDisabled_sensitiveImportantConversation() {
val entry =
@@ -266,7 +251,7 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun testUpdateIcons_cdFlagEnabled_sensitiveImportantConversation() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
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/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
index 25d1c377ecbd..7ed736158a53 100644
--- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -435,6 +435,8 @@ class FakeStatusBarService : IStatusBarService.Stub() {
override fun unbundleNotification(key: String) {}
+ override fun rebundleNotification(key: String) {}
+
companion object {
const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
const val SECONDARY_DISPLAY_ID = 2
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 5ac41ec6741c..f380789968f5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -23,11 +23,11 @@ import com.android.systemui.util.mockito.mock
val Kosmos.mockActivityTransitionAnimatorController by
Kosmos.Fixture { mock<ActivityTransitionAnimator.Controller>() }
-val Kosmos.activityTransitionAnimator by
+var Kosmos.activityTransitionAnimator by
Kosmos.Fixture {
ActivityTransitionAnimator(
// The main thread is checked in a bunch of places inside the different transitions
// animators, so we have to pass the real main executor here.
- mainExecutor = testCase.context.mainExecutor,
+ mainExecutor = testCase.context.mainExecutor
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
index a839f17aad82..c744eacfa3f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
@@ -33,6 +33,9 @@ class FakeAudioSharingRepository : AudioSharingRepository {
var sourceAdded: Boolean = false
private set
+ var audioSharingStarted: Boolean = false
+ private set
+
private var profile: LocalBluetoothLeBroadcast? = null
override val leAudioBroadcastProfile: LocalBluetoothLeBroadcast?
@@ -50,7 +53,13 @@ class FakeAudioSharingRepository : AudioSharingRepository {
override suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice) {}
- override suspend fun startAudioSharing() {}
+ override suspend fun startAudioSharing() {
+ audioSharingStarted = true
+ }
+
+ override suspend fun stopAudioSharing() {
+ audioSharingStarted = false
+ }
fun setAudioSharingAvailable(available: Boolean) {
mutableAvailable = available
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index 2a7e3e903737..490b89bf6b13 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -23,6 +23,7 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.util.time.systemClock
@@ -34,6 +35,8 @@ val Kosmos.deviceEntryHapticsInteractor by
DeviceEntryHapticsInteractor(
biometricSettingsRepository = biometricSettingsRepository,
deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ keyguardBypassInteractor = keyguardBypassInteractor,
deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
deviceEntrySourceInteractor = deviceEntrySourceInteractor,
fingerprintPropertyRepository = fingerprintPropertyRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 3fc60e339543..a64fc2413246 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -115,3 +115,6 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
interface FakeDisplayRepositoryModule {
@Binds fun bindFake(fake: FakeDisplayRepository): DisplayRepository
}
+
+val DisplayRepository.fake
+ get() = this as FakeDisplayRepository
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/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index afe48214832f..439df543b9fb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -54,8 +54,8 @@ var Kosmos.brightnessWarningToast: BrightnessWarningToast by
* Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
* that Kosmos instance
*/
-fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) {
- testScope.runTestWithSnapshots testBody@{ this@runTest.testBody() }
+fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) = let { kosmos ->
+ testScope.runTestWithSnapshots { kosmos.testBody() }
}
fun Kosmos.runCurrent() = testScope.runCurrent()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 82b5f6332b23..72c75000ebf4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.scene.domain.startable
import com.android.internal.logging.uiEventLogger
+import com.android.systemui.animation.activityTransitionAnimator
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
@@ -85,5 +86,6 @@ val Kosmos.sceneContainerStartable by Fixture {
vibratorHelper = vibratorHelper,
msdlPlayer = msdlPlayer,
disabledContentInteractor = disabledContentInteractor,
+ activityTransitionAnimator = activityTransitionAnimator,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt
index 69e215dcba6a..90897faaa6f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt
@@ -16,13 +16,28 @@
package com.android.systemui.statusbar.layout
+import android.content.applicationContext
+import com.android.systemui.SysUICutoutProvider
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.fake
import org.mockito.kotlin.mock
val Kosmos.mockStatusBarContentInsetsProvider by
Kosmos.Fixture { mock<StatusBarContentInsetsProvider>() }
-var Kosmos.statusBarContentInsetsProvider by Kosmos.Fixture { mockStatusBarContentInsetsProvider }
+val Kosmos.statusBarContentInsetsProvider by
+ Kosmos.Fixture {
+ StatusBarContentInsetsProviderImpl(
+ applicationContext,
+ configurationController.fake,
+ dumpManager,
+ commandRegistry,
+ mock<SysUICutoutProvider>(),
+ )
+ }
val Kosmos.fakeStatusBarContentInsetsProviderFactory by
Kosmos.Fixture { FakeStatusBarContentInsetsProviderFactory() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelKosmos.kt
new file mode 100644
index 000000000000..889d469489f2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.layout.ui.viewmodel
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.multiDisplayStatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.layout.statusBarContentInsetsProvider
+
+val Kosmos.statusBarContentInsetsViewModel by
+ Kosmos.Fixture { StatusBarContentInsetsViewModel(statusBarContentInsetsProvider) }
+
+val Kosmos.multiDisplayStatusBarContentInsetsViewModelStore by
+ Kosmos.Fixture {
+ MultiDisplayStatusBarContentInsetsViewModelStore(
+ applicationCoroutineScope,
+ displayRepository,
+ multiDisplayStatusBarContentInsetsProviderStore,
+ )
+ }
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..db7e31bb2cb6 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
@@ -26,6 +27,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModel
+import com.android.systemui.statusbar.layout.ui.viewmodel.multiDisplayStatusBarContentInsetsViewModelStore
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor
@@ -36,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStat
var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
Kosmos.Fixture {
HomeStatusBarViewModelImpl(
+ testableContext.displayId,
homeStatusBarInteractor,
homeStatusBarIconBlockListInteractor,
lightsOutInteractor,
@@ -51,6 +54,7 @@ var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
ongoingActivityChipsViewModel,
statusBarPopupChipsViewModel,
systemStatusEventAnimationInteractor,
+ multiDisplayStatusBarContentInsetsViewModelStore,
applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 282f5947636c..1e4701333857 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -25,3 +25,6 @@ val Kosmos.fakeConfigurationController: FakeConfigurationController by
Kosmos.Fixture { FakeConfigurationController() }
val Kosmos.statusBarConfigurationController: StatusBarConfigurationController by
Kosmos.Fixture { fakeConfigurationController }
+
+val ConfigurationController.fake
+ get() = this as FakeConfigurationController
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt
index 1ba5ddbf0337..fc0c92e974f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt
@@ -20,5 +20,5 @@ import com.android.settingslib.notification.data.repository.FakeZenModeRepositor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.zenModeRepository by Fixture { fakeZenModeRepository }
+var Kosmos.zenModeRepository by Fixture { fakeZenModeRepository }
val Kosmos.fakeZenModeRepository by Fixture { FakeZenModeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index ed5322ed098e..db19d6ee9077 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -39,7 +39,7 @@ val Kosmos.localMediaRepositoryFactory by
val Kosmos.mediaOutputActionsInteractor by
Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogManager) }
-val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
+var Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
val Kosmos.mediaOutputInteractor by
Kosmos.Fixture {
MediaOutputInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
index 712ec41bbf2d..3f2b47948c1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
@@ -19,4 +19,4 @@ package com.android.systemui.volume.data.repository
import com.android.systemui.kosmos.Kosmos
val Kosmos.fakeAudioRepository by Kosmos.Fixture { FakeAudioRepository() }
-val Kosmos.audioRepository by Kosmos.Fixture { fakeAudioRepository }
+var Kosmos.audioRepository by Kosmos.Fixture { fakeAudioRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/VolumeDialogKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/VolumeDialogKosmos.kt
new file mode 100644
index 000000000000..e2431934bc40
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/VolumeDialogKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.dagger.volumeDialogComponentFactory
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+
+val Kosmos.volumeDialog by
+ Kosmos.Fixture {
+ VolumeDialog(
+ context = applicationContext,
+ visibilityInteractor = volumeDialogVisibilityInteractor,
+ componentFactory = volumeDialogComponentFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponentKosmos.kt
new file mode 100644
index 000000000000..73e5d8d40985
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponentKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
+import com.android.systemui.volume.dialog.sliders.dagger.volumeDialogSliderComponentFactory
+import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
+import com.android.systemui.volume.dialog.ui.binder.volumeDialogViewBinder
+import kotlinx.coroutines.CoroutineScope
+
+val Kosmos.volumeDialogComponentFactory by
+ Kosmos.Fixture {
+ object : VolumeDialogComponent.Factory {
+ override fun create(scope: CoroutineScope): VolumeDialogComponent =
+ volumeDialogComponent
+ }
+ }
+val Kosmos.volumeDialogComponent by
+ Kosmos.Fixture {
+ object : VolumeDialogComponent {
+ override fun volumeDialogViewBinder(): VolumeDialogViewBinder = volumeDialogViewBinder
+
+ override fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory =
+ volumeDialogSliderComponentFactory
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt
index 291dfc0430e2..3d5698b193e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt
@@ -19,4 +19,4 @@ package com.android.systemui.volume.dialog.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.volume.dialog.data.VolumeDialogVisibilityRepository
-val Kosmos.volumeDialogVisibilityRepository by Kosmos.Fixture { VolumeDialogVisibilityRepository() }
+var Kosmos.volumeDialogVisibilityRepository by Kosmos.Fixture { VolumeDialogVisibilityRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
index db9c48d9be6f..8f122b57e9d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
@@ -16,8 +16,6 @@
package com.android.systemui.volume.dialog.domain.interactor
-import android.os.Handler
-import android.os.looper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
@@ -27,6 +25,6 @@ val Kosmos.volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor by
VolumeDialogCallbacksInteractor(
volumeDialogController = volumeDialogController,
coroutineScope = applicationCoroutineScope,
- bgHandler = Handler(looper),
+ bgHandler = null,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/VolumeDialogRingerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/VolumeDialogRingerViewBinderKosmos.kt
new file mode 100644
index 000000000000..7cbdc3d9f6ee
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/VolumeDialogRingerViewBinderKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.volumeDialogRingerDrawerViewModel
+
+val Kosmos.volumeDialogRingerViewBinder by
+ Kosmos.Fixture { VolumeDialogRingerViewBinder(volumeDialogRingerDrawerViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
index 44371b4615df..cf357b498621 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
@@ -20,5 +20,5 @@ import com.android.systemui.kosmos.Kosmos
val Kosmos.fakeVolumeDialogRingerFeedbackRepository by
Kosmos.Fixture { FakeVolumeDialogRingerFeedbackRepository() }
-val Kosmos.volumeDialogRingerFeedbackRepository by
+var Kosmos.volumeDialogRingerFeedbackRepository: VolumeDialogRingerFeedbackRepository by
Kosmos.Fixture { fakeVolumeDialogRingerFeedbackRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
index a494d04ec741..4bebf8911613 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
@@ -21,7 +21,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
import com.android.systemui.volume.data.repository.audioSystemRepository
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
-import com.android.systemui.volume.dialog.ringer.data.repository.fakeVolumeDialogRingerFeedbackRepository
+import com.android.systemui.volume.dialog.ringer.data.repository.volumeDialogRingerFeedbackRepository
val Kosmos.volumeDialogRingerInteractor by
Kosmos.Fixture {
@@ -30,6 +30,6 @@ val Kosmos.volumeDialogRingerInteractor by
volumeDialogStateInteractor = volumeDialogStateInteractor,
controller = volumeDialogController,
audioSystemRepository = audioSystemRepository,
- ringerFeedbackRepository = fakeVolumeDialogRingerFeedbackRepository,
+ ringerFeedbackRepository = volumeDialogRingerFeedbackRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractorKosmos.kt
new file mode 100644
index 000000000000..26b8bca6344b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractorKosmos.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.systemui.volume.dialog.settings.domain
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor
+
+val Kosmos.volumeDialogSettingsButtonInteractor by
+ Kosmos.Fixture {
+ VolumeDialogSettingsButtonInteractor(
+ applicationCoroutineScope,
+ deviceProvisionedController,
+ volumePanelGlobalStateInteractor,
+ volumeDialogVisibilityInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinderKosmos.kt
new file mode 100644
index 000000000000..f9e128ddd810
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinderKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.settings.ui.binder
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.settings.ui.viewmodel.volumeDialogSettingsButtonViewModel
+
+val Kosmos.volumeDialogSettingsButtonViewBinder by
+ Kosmos.Fixture { VolumeDialogSettingsButtonViewBinder(volumeDialogSettingsButtonViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModelKosmos.kt
new file mode 100644
index 000000000000..0ae3b037b50a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.settings.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.dialog.settings.domain.volumeDialogSettingsButtonInteractor
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+
+val Kosmos.volumeDialogSettingsButtonViewModel by
+ Kosmos.Fixture {
+ VolumeDialogSettingsButtonViewModel(
+ applicationContext,
+ testScope.testScheduler,
+ applicationCoroutineScope,
+ mediaOutputInteractor,
+ mediaDeviceSessionInteractor,
+ volumeDialogSettingsButtonInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
new file mode 100644
index 000000000000..4f79f7b4b41a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.dagger
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.volumeDialogOverscrollViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderHapticsViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderViewBinder
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor
+
+private val Kosmos.mutableSliderComponentKosmoses: MutableMap<VolumeDialogSliderType, Kosmos> by
+ Kosmos.Fixture { mutableMapOf() }
+
+val Kosmos.volumeDialogSliderComponentFactory by
+ Kosmos.Fixture {
+ object : VolumeDialogSliderComponent.Factory {
+ override fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderComponent =
+ volumeDialogSliderComponent(sliderType)
+ }
+ }
+
+fun Kosmos.volumeDialogSliderComponent(type: VolumeDialogSliderType): VolumeDialogSliderComponent {
+ return object : VolumeDialogSliderComponent {
+
+ private val localKosmos
+ get() =
+ mutableSliderComponentKosmoses.getOrPut(type) {
+ Kosmos().also {
+ it.setupVolumeDialogSliderComponent(this@volumeDialogSliderComponent, type)
+ }
+ }
+
+ override fun sliderViewBinder(): VolumeDialogSliderViewBinder =
+ localKosmos.volumeDialogSliderViewBinder
+
+ override fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder =
+ localKosmos.volumeDialogSliderHapticsViewBinder
+
+ override fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder =
+ localKosmos.volumeDialogOverscrollViewBinder
+ }
+}
+
+private fun Kosmos.setupVolumeDialogSliderComponent(
+ parentKosmos: Kosmos,
+ type: VolumeDialogSliderType,
+) {
+ volumeDialogSliderType = type
+ applicationContext = parentKosmos.applicationContext
+ testScope = parentKosmos.testScope
+
+ volumeDialogController = parentKosmos.volumeDialogController
+ mediaControllerInteractor = parentKosmos.mediaControllerInteractor
+ mediaControllerRepository = parentKosmos.mediaControllerRepository
+ zenModeRepository = parentKosmos.zenModeRepository
+ volumeDialogVisibilityRepository = parentKosmos.volumeDialogVisibilityRepository
+ audioRepository = parentKosmos.audioRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinderKosmos.kt
new file mode 100644
index 000000000000..13d6ca9732d1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinderKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogOverscrollViewModel
+
+val Kosmos.volumeDialogOverscrollViewBinder by
+ Kosmos.Fixture { VolumeDialogOverscrollViewBinder(volumeDialogOverscrollViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt
new file mode 100644
index 000000000000..d6845b1ff7e3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.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.systemui.volume.dialog.sliders.ui
+
+import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel
+
+val Kosmos.volumeDialogSliderHapticsViewBinder by
+ Kosmos.Fixture {
+ VolumeDialogSliderHapticsViewBinder(
+ volumeDialogSliderInputEventsViewModel,
+ vibratorHelper,
+ msdlPlayer,
+ systemClock,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt
new file mode 100644
index 000000000000..c6db717e004f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderViewModel
+
+val Kosmos.volumeDialogSliderViewBinder by
+ Kosmos.Fixture {
+ VolumeDialogSliderViewBinder(
+ volumeDialogSliderViewModel,
+ volumeDialogSliderInputEventsViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinderKosmos.kt
new file mode 100644
index 000000000000..83527d994e70
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinderKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSlidersViewModel
+
+val Kosmos.volumeDialogSlidersViewBinder by
+ Kosmos.Fixture { VolumeDialogSlidersViewBinder(volumeDialogSlidersViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModelKosmos.kt
new file mode 100644
index 000000000000..fe2f3d806b6a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModelKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor
+
+val Kosmos.volumeDialogOverscrollViewModel by
+ Kosmos.Fixture {
+ VolumeDialogOverscrollViewModel(applicationContext, volumeDialogSliderInputEventsInteractor)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
new file mode 100644
index 000000000000..09f9f1c6362e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
+
+val Kosmos.volumeDialogSliderIconProvider by
+ Kosmos.Fixture {
+ VolumeDialogSliderIconProvider(
+ context = applicationContext,
+ audioVolumeInteractor = audioVolumeInteractor,
+ zenModeInteractor = zenModeInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt
new file mode 100644
index 000000000000..2de0e8f76a4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor
+
+val Kosmos.volumeDialogSliderInputEventsViewModel by
+ Kosmos.Fixture {
+ VolumeDialogSliderInputEventsViewModel(
+ applicationCoroutineScope,
+ volumeDialogSliderInputEventsInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
new file mode 100644
index 000000000000..63cd440a8633
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.time.systemClock
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInteractor
+
+val Kosmos.volumeDialogSliderViewModel by
+ Kosmos.Fixture {
+ VolumeDialogSliderViewModel(
+ interactor = volumeDialogSliderInteractor,
+ visibilityInteractor = volumeDialogVisibilityInteractor,
+ coroutineScope = applicationCoroutineScope,
+ volumeDialogSliderIconProvider = volumeDialogSliderIconProvider,
+ systemClock = systemClock,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
new file mode 100644
index 000000000000..5531f7608b69
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.dialog.sliders.dagger.volumeDialogSliderComponentFactory
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSlidersInteractor
+
+val Kosmos.volumeDialogSlidersViewModel by
+ Kosmos.Fixture {
+ VolumeDialogSlidersViewModel(
+ applicationCoroutineScope,
+ volumeDialogSlidersInteractor,
+ volumeDialogSliderComponentFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
new file mode 100644
index 000000000000..dc09e3233b1e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.binder
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.ringer.volumeDialogRingerViewBinder
+import com.android.systemui.volume.dialog.settings.ui.binder.volumeDialogSettingsButtonViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSlidersViewBinder
+import com.android.systemui.volume.dialog.ui.utils.jankListenerFactory
+import com.android.systemui.volume.dialog.ui.viewmodel.volumeDialogViewModel
+import com.android.systemui.volume.dialog.utils.volumeTracer
+
+val Kosmos.volumeDialogViewBinder by
+ Kosmos.Fixture {
+ VolumeDialogViewBinder(
+ applicationContext.resources,
+ volumeDialogViewModel,
+ jankListenerFactory,
+ volumeTracer,
+ volumeDialogRingerViewBinder,
+ volumeDialogSlidersViewBinder,
+ volumeDialogSettingsButtonViewBinder,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactoryKosmos.kt
new file mode 100644
index 000000000000..35ec5d3cc9af
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.utils
+
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.jankListenerFactory by Kosmos.Fixture { JankListenerFactory(interactionJankMonitor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModelKosmos.kt
new file mode 100644
index 000000000000..05ef462d4998
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.devicePostureController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSlidersInteractor
+
+val Kosmos.volumeDialogViewModel by
+ Kosmos.Fixture {
+ VolumeDialogViewModel(
+ applicationContext,
+ volumeDialogVisibilityInteractor,
+ volumeDialogSlidersInteractor,
+ volumeDialogStateInteractor,
+ devicePostureController,
+ configurationController,
+ )
+ }
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index 1370b0678cc5..97574e6e35e3 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -39,9 +39,7 @@ java_library {
name: "connectivity-utils-service-vcn-internal",
sdk_version: "module_current",
min_sdk_version: "30",
- srcs: [
- ":framework-connectivity-shared-srcs",
- ],
+ srcs: ["service-utils/**/*.java"],
libs: [
"framework-annotations-lib",
"unsupportedappusage",
diff --git a/packages/Vcn/service-b/service-utils/android/util/LocalLog.java b/packages/Vcn/service-b/service-utils/android/util/LocalLog.java
new file mode 100644
index 000000000000..5955d930aab1
--- /dev/null
+++ b/packages/Vcn/service-b/service-utils/android/util/LocalLog.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.SystemClock;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+/**
+ * @hide
+ */
+// Exported to Mainline modules; cannot use annotations
+// @android.ravenwood.annotation.RavenwoodKeepWholeClass
+// TODO: b/374174952 This is an exact copy of frameworks/base/core/java/android/util/LocalLog.java.
+// This file is only used in "service-connectivity-b-platform" before the VCN modularization flag
+// is fully ramped. When the flag is fully ramped and the development is finalized, this file can
+// be removed.
+public final class LocalLog {
+
+ private final Deque<String> mLog;
+ private final int mMaxLines;
+
+ /**
+ * {@code true} to use log timestamps expressed in local date/time, {@code false} to use log
+ * timestamped expressed with the elapsed realtime clock and UTC system clock. {@code false} is
+ * useful when logging behavior that modifies device time zone or system clock.
+ */
+ private final boolean mUseLocalTimestamps;
+
+ @UnsupportedAppUsage
+ public LocalLog(int maxLines) {
+ this(maxLines, true /* useLocalTimestamps */);
+ }
+
+ public LocalLog(int maxLines, boolean useLocalTimestamps) {
+ mMaxLines = Math.max(0, maxLines);
+ mLog = new ArrayDeque<>(mMaxLines);
+ mUseLocalTimestamps = useLocalTimestamps;
+ }
+
+ @UnsupportedAppUsage
+ public void log(String msg) {
+ if (mMaxLines <= 0) {
+ return;
+ }
+ final String logLine;
+ if (mUseLocalTimestamps) {
+ logLine = LocalDateTime.now() + " - " + msg;
+ } else {
+ logLine = Duration.ofMillis(SystemClock.elapsedRealtime())
+ + " / " + Instant.now() + " - " + msg;
+ }
+ append(logLine);
+ }
+
+ private synchronized void append(String logLine) {
+ while (mLog.size() >= mMaxLines) {
+ mLog.remove();
+ }
+ mLog.add(logLine);
+ }
+
+ @UnsupportedAppUsage
+ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ dump(pw);
+ }
+
+ public synchronized void dump(PrintWriter pw) {
+ dump("", pw);
+ }
+
+ /**
+ * Dumps the content of local log to print writer with each log entry predeced with indent
+ *
+ * @param indent indent that precedes each log entry
+ * @param pw printer writer to write into
+ */
+ public synchronized void dump(String indent, PrintWriter pw) {
+ Iterator<String> itr = mLog.iterator();
+ while (itr.hasNext()) {
+ pw.printf("%s%s\n", indent, itr.next());
+ }
+ }
+
+ public synchronized void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ reverseDump(pw);
+ }
+
+ public synchronized void reverseDump(PrintWriter pw) {
+ Iterator<String> itr = mLog.descendingIterator();
+ while (itr.hasNext()) {
+ pw.println(itr.next());
+ }
+ }
+
+ // @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public synchronized void clear() {
+ mLog.clear();
+ }
+
+ public static class ReadOnlyLocalLog {
+ private final LocalLog mLog;
+ ReadOnlyLocalLog(LocalLog log) {
+ mLog = log;
+ }
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mLog.dump(pw);
+ }
+ public void dump(PrintWriter pw) {
+ mLog.dump(pw);
+ }
+ public void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mLog.reverseDump(pw);
+ }
+ public void reverseDump(PrintWriter pw) {
+ mLog.reverseDump(pw);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ReadOnlyLocalLog readOnlyLocalLog() {
+ return new ReadOnlyLocalLog(this);
+ }
+} \ No newline at end of file
diff --git a/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java b/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java
new file mode 100644
index 000000000000..7db62f8e9ffc
--- /dev/null
+++ b/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java
@@ -0,0 +1,142 @@
+/*
+ * 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.internal.util;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+ /**
+ * An AlarmListener that sends the specified message to a Handler and keeps the system awake until
+ * the message is processed.
+ *
+ * This is useful when using the AlarmManager direct callback interface to wake up the system and
+ * request that an object whose API consists of messages (such as a StateMachine) perform some
+ * action.
+ *
+ * In this situation, using AlarmManager.onAlarmListener by itself will wake up the system to send
+ * the message, but does not guarantee that the system will be awake until the target object has
+ * processed it. This is because as soon as the onAlarmListener sends the message and returns, the
+ * AlarmManager releases its wakelock and the system is free to go to sleep again.
+ */
+// TODO: b/374174952 This is an exact copy of
+// frameworks/base/core/java/com/android/internal/util/WakeupMessage.java.
+// This file is only used in "service-connectivity-b-platform" before the VCN modularization flag
+// is fully ramped. When the flag is fully ramped and the development is finalized, this file can
+// be removed.
+public class WakeupMessage implements AlarmManager.OnAlarmListener {
+ private final AlarmManager mAlarmManager;
+
+ @VisibleForTesting
+ protected final Handler mHandler;
+ @VisibleForTesting
+ protected final String mCmdName;
+ @VisibleForTesting
+ protected final int mCmd, mArg1, mArg2;
+ @VisibleForTesting
+ protected final Object mObj;
+ private final Runnable mRunnable;
+ private boolean mScheduled;
+
+ public WakeupMessage(Context context, Handler handler,
+ String cmdName, int cmd, int arg1, int arg2, Object obj) {
+ mAlarmManager = getAlarmManager(context);
+ mHandler = handler;
+ mCmdName = cmdName;
+ mCmd = cmd;
+ mArg1 = arg1;
+ mArg2 = arg2;
+ mObj = obj;
+ mRunnable = null;
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
+ this(context, handler, cmdName, cmd, arg1, 0, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler,
+ String cmdName, int cmd, int arg1, int arg2) {
+ this(context, handler, cmdName, cmd, arg1, arg2, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
+ this(context, handler, cmdName, cmd, 0, 0, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, Runnable runnable) {
+ mAlarmManager = getAlarmManager(context);
+ mHandler = handler;
+ mCmdName = cmdName;
+ mCmd = 0;
+ mArg1 = 0;
+ mArg2 = 0;
+ mObj = null;
+ mRunnable = runnable;
+ }
+
+ private static AlarmManager getAlarmManager(Context context) {
+ return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ }
+
+ /**
+ * Schedule the message to be delivered at the time in milliseconds of the
+ * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
+ * the device when it goes off. If schedule is called multiple times without the message being
+ * dispatched then the alarm is rescheduled to the new time.
+ */
+ public synchronized void schedule(long when) {
+ mAlarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP, when, mCmdName, this, mHandler);
+ mScheduled = true;
+ }
+
+ /**
+ * Cancel all pending messages. This includes alarms that may have been fired, but have not been
+ * run on the handler yet.
+ */
+ public synchronized void cancel() {
+ if (mScheduled) {
+ mAlarmManager.cancel(this);
+ mScheduled = false;
+ }
+ }
+
+ @Override
+ public void onAlarm() {
+ // Once this method is called the alarm has already been fired and removed from
+ // AlarmManager (it is still partially tracked, but only for statistics). The alarm can now
+ // be marked as unscheduled so that it can be rescheduled in the message handler.
+ final boolean stillScheduled;
+ synchronized (this) {
+ stillScheduled = mScheduled;
+ mScheduled = false;
+ }
+ if (stillScheduled) {
+ Message msg;
+ if (mRunnable == null) {
+ msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+ } else {
+ msg = Message.obtain(mHandler, mRunnable);
+ }
+ mHandler.dispatchMessage(msg);
+ msg.recycle();
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt b/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
index 36307277b4b9..6ec39d953266 100644
--- a/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
+++ b/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
@@ -1,5 +1,2 @@
-rule android.util.IndentingPrintWriter android.net.vcn.module.repackaged.android.util.IndentingPrintWriter
rule android.util.LocalLog android.net.vcn.module.repackaged.android.util.LocalLog
-rule com.android.internal.util.IndentingPrintWriter android.net.vcn.module.repackaged.com.android.internal.util.IndentingPrintWriter
-rule com.android.internal.util.MessageUtils android.net.vcn.module.repackaged.com.android.internal.util.MessageUtils
rule com.android.internal.util.WakeupMessage android.net.vcn.module.repackaged.com.android.internal.util.WakeupMessage \ No newline at end of file
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 59043a8356ae..8e998426685b 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -345,14 +345,41 @@ java_library {
],
}
+// We define our own version of platform_compat_config's here, because:
+// - The original version (e.g. "framework-platform-compat-config) is built from
+// the output file of the device side jar, rather than the host jar, meaning
+// they're slow to build because they depend on D8/R8 output.
+// - The original services one ("services-platform-compat-config") is built from services.jar,
+// which includes service.permission, which is very slow to rebuild because of kotlin.
+//
+// Because we're re-defining the same compat-IDs that are defined elsewhere,
+// they should all have `include_in_merged_xml: false`. Otherwise, generating
+// merged_compat_config.xml would fail due to duplicate IDs.
+//
+// These module names must end with "compat-config" because these will be used as the filename,
+// and at runtime, we only loads files that match `*compat-config.xml`.
+platform_compat_config {
+ name: "ravenwood-framework-platform-compat-config",
+ src: ":framework-minus-apex-for-host",
+ include_in_merged_xml: false,
+ visibility: ["//visibility:private"],
+}
+
+platform_compat_config {
+ name: "ravenwood-services.core-platform-compat-config",
+ src: ":services.core-for-host",
+ include_in_merged_xml: false,
+ visibility: ["//visibility:private"],
+}
+
filegroup {
name: "ravenwood-data",
device_common_srcs: [
":system-build.prop",
":framework-res",
":ravenwood-empty-res",
- ":framework-platform-compat-config",
- ":services-platform-compat-config",
+ ":ravenwood-framework-platform-compat-config",
+ ":ravenwood-services.core-platform-compat-config",
"texts/ravenwood-build.prop",
],
device_first_srcs: [
@@ -616,6 +643,10 @@ android_ravenwood_libgroup {
"android.test.mock.ravenwood",
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
+
+ // Note, when we include other services.* jars, we'll need to add
+ // platform_compat_config for that module too.
+ // See ravenwood-services.core-platform-compat-config above.
"services.core.ravenwood-jarjar",
"services.fakes.ravenwood-jarjar",
diff --git a/ravenwood/CleanSpec.mk b/ravenwood/CleanSpec.mk
new file mode 100644
index 000000000000..50d2fab40326
--- /dev/null
+++ b/ravenwood/CleanSpec.mk
@@ -0,0 +1,45 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# *****************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THE BANNER
+# *****************************************************************
+
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime)
+
+# ******************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
+# ******************************************************************
diff --git a/ravenwood/tools/hoststubgen/scripts/dump-jar b/ravenwood/tools/hoststubgen/scripts/dump-jar
index 87652451359d..998357b70dff 100755
--- a/ravenwood/tools/hoststubgen/scripts/dump-jar
+++ b/ravenwood/tools/hoststubgen/scripts/dump-jar
@@ -89,7 +89,7 @@ filter_output() {
# - Some other transient lines
# - Sometimes the javap shows mysterious warnings, so remove them too.
#
- # `/PATTERN-1/,/PATTERN-1/{//!d}` is a trick to delete lines between two patterns, without
+ # `/PATTERN-1/,/PATTERN-2/{//!d}` is a trick to delete lines between two patterns, without
# the start and the end lines.
sed -e 's/#[0-9][0-9]*/#x/g' \
-e 's/^\( *\)[0-9][0-9]*:/\1x:/' \
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 6d8d7b768b91..cc704b2b32ed 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -27,7 +27,7 @@ import com.android.hoststubgen.filters.ImplicitOutputFilter
import com.android.hoststubgen.filters.KeepNativeFilter
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.filters.SanitizationFilter
-import com.android.hoststubgen.filters.TextFileFilterPolicyParser
+import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
import com.android.hoststubgen.filters.printAsTextPolicy
import com.android.hoststubgen.utils.ClassFilter
import com.android.hoststubgen.visitors.BaseAdapter
@@ -179,9 +179,9 @@ class HostStubGen(val options: HostStubGenOptions) {
// Next, "text based" filter, which allows to override polices without touching
// the target code.
if (options.policyOverrideFiles.isNotEmpty()) {
- val parser = TextFileFilterPolicyParser(allClasses, filter)
- options.policyOverrideFiles.forEach(parser::parse)
- filter = parser.createOutputFilter()
+ val builder = TextFileFilterPolicyBuilder(allClasses, filter)
+ options.policyOverrideFiles.forEach(builder::parse)
+ filter = builder.createOutputFilter()
}
// Apply the implicit filter.
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 7462a8ce12c5..be1b6ca93869 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -23,10 +23,12 @@ import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.log
import com.android.hoststubgen.normalizeTextLine
import com.android.hoststubgen.whitespaceRegex
-import java.io.File
+import org.objectweb.asm.tree.ClassNode
+import java.io.BufferedReader
+import java.io.FileReader
import java.io.PrintWriter
+import java.io.Reader
import java.util.regex.Pattern
-import org.objectweb.asm.tree.ClassNode
/**
* Print a class node as a "keep" policy.
@@ -48,7 +50,7 @@ fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) {
private const val FILTER_REASON = "file-override"
-private enum class SpecialClass {
+enum class SpecialClass {
NotSpecial,
Aidl,
FeatureFlags,
@@ -56,10 +58,58 @@ private enum class SpecialClass {
RFile,
}
-class TextFileFilterPolicyParser(
+/**
+ * This receives [TextFileFilterPolicyBuilder] parsing result.
+ */
+interface PolicyFileProcessor {
+ /** "package" directive. */
+ fun onPackage(name: String, policy: FilterPolicyWithReason)
+
+ /** "rename" directive. */
+ fun onRename(pattern: Pattern, prefix: String)
+
+ /** "class" directive. */
+ fun onSimpleClassStart(className: String)
+ fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason)
+ fun onSimpleClassEnd(className: String)
+
+ fun onSubClassPolicy(superClassName: String, policy: FilterPolicyWithReason)
+ fun onRedirectionClass(fromClassName: String, toClassName: String)
+ fun onClassLoadHook(className: String, callback: String)
+ fun onSpecialClassPolicy(type: SpecialClass, policy: FilterPolicyWithReason)
+
+ /** "field" directive. */
+ fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason)
+
+ /** "method" directive. */
+ fun onSimpleMethodPolicy(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason,
+ )
+ fun onMethodInClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ targetName: String,
+ policy: FilterPolicyWithReason,
+ )
+ fun onMethodOutClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
+ policy: FilterPolicyWithReason,
+ )
+}
+
+class TextFileFilterPolicyBuilder(
private val classes: ClassNodes,
fallback: OutputFilter
) {
+ private val parser = TextFileFilterPolicyParser()
+
private val subclassFilter = SubclassFilter(classes, fallback)
private val packageFilter = PackageFilter(subclassFilter)
private val imf = InMemoryOutputFilter(classes, packageFilter)
@@ -71,30 +121,19 @@ class TextFileFilterPolicyParser(
private val methodReplaceSpec =
mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>()
- private lateinit var currentClassName: String
-
/**
- * Read a given "policy" file and return as an [OutputFilter]
+ * Parse a given policy file. This method can be called multiple times to read from
+ * multiple files. To get the resulting filter, use [createOutputFilter]
*/
fun parse(file: String) {
- log.i("Loading offloaded annotations from $file ...")
- log.withIndent {
- var lineNo = 0
- try {
- File(file).forEachLine {
- lineNo++
- val line = normalizeTextLine(it)
- if (line.isEmpty()) {
- return@forEachLine // skip empty lines.
- }
- parseLine(line)
- }
- } catch (e: ParseException) {
- throw e.withSourceInfo(file, lineNo)
- }
- }
+ // We may parse multiple files, but we reuse the same parser, because the parser
+ // will make sure there'll be no dupplicating "special class" policies.
+ parser.parse(FileReader(file), file, Processor())
}
+ /**
+ * Generate the resulting [OutputFilter].
+ */
fun createOutputFilter(): OutputFilter {
var ret: OutputFilter = imf
if (typeRenameSpec.isNotEmpty()) {
@@ -112,14 +151,200 @@ class TextFileFilterPolicyParser(
return ret
}
+ private inner class Processor : PolicyFileProcessor {
+ override fun onPackage(name: String, policy: FilterPolicyWithReason) {
+ packageFilter.addPolicy(name, policy)
+ }
+
+ override fun onRename(pattern: Pattern, prefix: String) {
+ typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
+ pattern, prefix
+ )
+ }
+
+ override fun onSimpleClassStart(className: String) {
+ }
+
+ override fun onSimpleClassEnd(className: String) {
+ }
+
+ override fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason) {
+ imf.setPolicyForClass(className, policy)
+ }
+
+ override fun onSubClassPolicy(
+ superClassName: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ log.i("class extends $superClassName")
+ subclassFilter.addPolicy( superClassName, policy)
+ }
+
+ override fun onRedirectionClass(fromClassName: String, toClassName: String) {
+ imf.setRedirectionClass(fromClassName, toClassName)
+ }
+
+ override fun onClassLoadHook(className: String, callback: String) {
+ imf.setClassLoadHook(className, callback)
+ }
+
+ override fun onSpecialClassPolicy(
+ type: SpecialClass,
+ policy: FilterPolicyWithReason,
+ ) {
+ log.i("class special $type $policy")
+ when (type) {
+ SpecialClass.NotSpecial -> {} // Shouldn't happen
+
+ SpecialClass.Aidl -> {
+ aidlPolicy = policy
+ }
+
+ SpecialClass.FeatureFlags -> {
+ featureFlagsPolicy = policy
+ }
+
+ SpecialClass.Sysprops -> {
+ syspropsPolicy = policy
+ }
+
+ SpecialClass.RFile -> {
+ rFilePolicy = policy
+ }
+ }
+ }
+
+ override fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
+ imf.setPolicyForField(className, fieldName, policy)
+ }
+
+ override fun onSimpleMethodPolicy(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ imf.setPolicyForMethod(className, methodName, methodDesc, policy)
+ }
+
+ override fun onMethodInClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ targetName: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ imf.setPolicyForMethod(className, methodName, methodDesc, policy)
+
+ // Make sure to keep the target method.
+ imf.setPolicyForMethod(
+ className,
+ targetName,
+ methodDesc,
+ FilterPolicy.Keep.withReason(FILTER_REASON)
+ )
+ // Set up the rename.
+ imf.setRenameTo(className, targetName, methodDesc, methodName)
+ }
+
+ override fun onMethodOutClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
+ policy: FilterPolicyWithReason,
+ ) {
+ imf.setPolicyForMethod(className, methodName, methodDesc, policy)
+ methodReplaceSpec.add(replaceSpec)
+ }
+ }
+}
+
+/**
+ * Parses a filer policy text file.
+ */
+class TextFileFilterPolicyParser {
+ private lateinit var processor: PolicyFileProcessor
+ private var currentClassName: String? = null
+
+ private var aidlPolicy: FilterPolicyWithReason? = null
+ private var featureFlagsPolicy: FilterPolicyWithReason? = null
+ private var syspropsPolicy: FilterPolicyWithReason? = null
+ private var rFilePolicy: FilterPolicyWithReason? = null
+
+ /** Name of the file that's currently being processed. */
+ var filename: String? = null
+ private set
+
+ /** 1-based line number in the current file */
+ var lineNumber = -1
+ private set
+
+ /**
+ * Parse a given "policy" file.
+ */
+ fun parse(reader: Reader, inputName: String, processor: PolicyFileProcessor) {
+ filename = inputName
+
+ log.i("Parsing text policy file $inputName ...")
+ this.processor = processor
+ BufferedReader(reader).use { rd ->
+ lineNumber = 0
+ try {
+ while (true) {
+ var line = rd.readLine()
+ if (line == null) {
+ break
+ }
+ lineNumber++
+ line = normalizeTextLine(line) // Remove comment and trim.
+ if (line.isEmpty()) {
+ continue
+ }
+ parseLine(line)
+ }
+ finishLastClass()
+ } catch (e: ParseException) {
+ throw e.withSourceInfo(inputName, lineNumber)
+ }
+ }
+ }
+
+ private fun finishLastClass() {
+ currentClassName?.let { className ->
+ processor.onSimpleClassEnd(className)
+ currentClassName = null
+ }
+ }
+
+ private fun ensureInClass(directive: String): String {
+ return currentClassName ?:
+ throw ParseException("Directive '$directive' must follow a 'class' directive")
+ }
+
private fun parseLine(line: String) {
val fields = line.split(whitespaceRegex).toTypedArray()
when (fields[0].lowercase()) {
- "p", "package" -> parsePackage(fields)
- "c", "class" -> parseClass(fields)
- "f", "field" -> parseField(fields)
- "m", "method" -> parseMethod(fields)
- "r", "rename" -> parseRename(fields)
+ "p", "package" -> {
+ finishLastClass()
+ parsePackage(fields)
+ }
+ "c", "class" -> {
+ finishLastClass()
+ parseClass(fields)
+ }
+ "f", "field" -> {
+ ensureInClass("field")
+ parseField(fields)
+ }
+ "m", "method" -> {
+ ensureInClass("method")
+ parseMethod(fields)
+ }
+ "r", "rename" -> {
+ finishLastClass()
+ parseRename(fields)
+ }
else -> throw ParseException("Unknown directive \"${fields[0]}\"")
}
}
@@ -184,20 +409,20 @@ class TextFileFilterPolicyParser(
if (!policy.isUsableWithClasses) {
throw ParseException("Package can't have policy '$policy'")
}
- packageFilter.addPolicy(name, policy.withReason(FILTER_REASON))
+ processor.onPackage(name, policy.withReason(FILTER_REASON))
}
private fun parseClass(fields: Array<String>) {
if (fields.size < 3) {
throw ParseException("Class ('c') expects 2 fields.")
}
- currentClassName = fields[1]
+ val className = fields[1]
// superClass is set when the class name starts with a "*".
- val superClass = resolveExtendingClass(currentClassName)
+ val superClass = resolveExtendingClass(className)
// :aidl, etc?
- val classType = resolveSpecialClass(currentClassName)
+ val classType = resolveSpecialClass(className)
if (fields[2].startsWith("!")) {
if (classType != SpecialClass.NotSpecial) {
@@ -208,7 +433,8 @@ class TextFileFilterPolicyParser(
}
// It's a redirection class.
val toClass = fields[2].substring(1)
- imf.setRedirectionClass(currentClassName, toClass)
+
+ processor.onRedirectionClass(className, toClass)
} else if (fields[2].startsWith("~")) {
if (classType != SpecialClass.NotSpecial) {
// We could support it, but not needed at least for now.
@@ -218,7 +444,8 @@ class TextFileFilterPolicyParser(
}
// It's a class-load hook
val callback = fields[2].substring(1)
- imf.setClassLoadHook(currentClassName, callback)
+
+ processor.onClassLoadHook(className, callback)
} else {
val policy = parsePolicy(fields[2])
if (!policy.isUsableWithClasses) {
@@ -229,26 +456,27 @@ class TextFileFilterPolicyParser(
SpecialClass.NotSpecial -> {
// TODO: Duplicate check, etc
if (superClass == null) {
- imf.setPolicyForClass(
- currentClassName, policy.withReason(FILTER_REASON)
- )
+ currentClassName = className
+ processor.onSimpleClassStart(className)
+ processor.onSimpleClassPolicy(className, policy.withReason(FILTER_REASON))
} else {
- subclassFilter.addPolicy(
+ processor.onSubClassPolicy(
superClass,
- policy.withReason("extends $superClass")
+ policy.withReason("extends $superClass"),
)
}
}
-
SpecialClass.Aidl -> {
if (aidlPolicy != null) {
throw ParseException(
"Policy for AIDL classes already defined"
)
}
- aidlPolicy = policy.withReason(
+ val p = policy.withReason(
"$FILTER_REASON (special-class AIDL)"
)
+ processor.onSpecialClassPolicy(classType, p)
+ aidlPolicy = p
}
SpecialClass.FeatureFlags -> {
@@ -257,9 +485,11 @@ class TextFileFilterPolicyParser(
"Policy for feature flags already defined"
)
}
- featureFlagsPolicy = policy.withReason(
+ val p = policy.withReason(
"$FILTER_REASON (special-class feature flags)"
)
+ processor.onSpecialClassPolicy(classType, p)
+ featureFlagsPolicy = p
}
SpecialClass.Sysprops -> {
@@ -268,9 +498,11 @@ class TextFileFilterPolicyParser(
"Policy for sysprops already defined"
)
}
- syspropsPolicy = policy.withReason(
+ val p = policy.withReason(
"$FILTER_REASON (special-class sysprops)"
)
+ processor.onSpecialClassPolicy(classType, p)
+ syspropsPolicy = p
}
SpecialClass.RFile -> {
@@ -279,9 +511,11 @@ class TextFileFilterPolicyParser(
"Policy for R file already defined"
)
}
- rFilePolicy = policy.withReason(
+ val p = policy.withReason(
"$FILTER_REASON (special-class R file)"
)
+ processor.onSpecialClassPolicy(classType, p)
+ rFilePolicy = p
}
}
}
@@ -296,17 +530,16 @@ class TextFileFilterPolicyParser(
if (!policy.isUsableWithFields) {
throw ParseException("Field can't have policy '$policy'")
}
- require(this::currentClassName.isInitialized)
// TODO: Duplicate check, etc
- imf.setPolicyForField(currentClassName, name, policy.withReason(FILTER_REASON))
+ processor.onField(currentClassName!!, name, policy.withReason(FILTER_REASON))
}
private fun parseMethod(fields: Array<String>) {
if (fields.size < 3 || fields.size > 4) {
throw ParseException("Method ('m') expects 3 or 4 fields.")
}
- val name = fields[1]
+ val methodName = fields[1]
val signature: String
val policyStr: String
if (fields.size <= 3) {
@@ -323,44 +556,48 @@ class TextFileFilterPolicyParser(
throw ParseException("Method can't have policy '$policy'")
}
- require(this::currentClassName.isInitialized)
+ val className = currentClassName!!
- imf.setPolicyForMethod(
- currentClassName, name, signature,
- policy.withReason(FILTER_REASON)
- )
- if (policy == FilterPolicy.Substitute) {
- val fromName = policyStr.substring(1)
+ val policyWithReason = policy.withReason(FILTER_REASON)
+ if (policy != FilterPolicy.Substitute) {
+ processor.onSimpleMethodPolicy(className, methodName, signature, policyWithReason)
+ } else {
+ val targetName = policyStr.substring(1)
- if (fromName == name) {
+ if (targetName == methodName) {
throw ParseException(
"Substitution must have a different name"
)
}
- // Set the policy for the "from" method.
- imf.setPolicyForMethod(
- currentClassName, fromName, signature,
- FilterPolicy.Keep.withReason(FILTER_REASON)
- )
-
- val classAndMethod = splitWithLastPeriod(fromName)
+ val classAndMethod = splitWithLastPeriod(targetName)
if (classAndMethod != null) {
// If the substitution target contains a ".", then
// it's a method call redirect.
- methodReplaceSpec.add(
- TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
- currentClassName.toJvmClassName(),
- name,
+ val spec = TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
+ currentClassName!!.toJvmClassName(),
+ methodName,
signature,
classAndMethod.first.toJvmClassName(),
classAndMethod.second,
)
+ processor.onMethodOutClassReplace(
+ className,
+ methodName,
+ signature,
+ spec,
+ policyWithReason,
)
} else {
// It's an in-class replace.
// ("@RavenwoodReplace" equivalent)
- imf.setRenameTo(currentClassName, fromName, signature, name)
+ processor.onMethodInClassReplace(
+ className,
+ methodName,
+ signature,
+ targetName,
+ policyWithReason,
+ )
}
}
}
@@ -378,7 +615,7 @@ class TextFileFilterPolicyParser(
// applied. (Which is needed for services.jar)
val prefix = fields[2].trimStart('/')
- typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
+ processor.onRename(
pattern, prefix
)
}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
index b389a67a8e4c..8408a18142eb 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
@@ -34,10 +34,11 @@ source "${0%/*}"/../common.sh
SCRIPT_NAME="${0##*/}"
-GOLDEN_DIR=golden-output
+GOLDEN_DIR=${GOLDEN_DIR:-golden-output}
mkdir -p $GOLDEN_DIR
-DIFF_CMD=${DIFF:-diff -u --ignore-blank-lines --ignore-space-change}
+# TODO(b/388562869) We shouldn't need `--ignore-matching-lines`, but the golden files may not have the "Constant pool:" lines.
+DIFF_CMD=${DIFF_CMD:-diff -u --ignore-blank-lines --ignore-space-change --ignore-matching-lines='^\(Constant.pool:\|{\)$'}
update=0
three_way=0
@@ -62,7 +63,7 @@ done
shift $(($OPTIND - 1))
# Build the dump files, which are the input of this test.
-run m dump-jar tiny-framework-dump-test
+run ${BUILD_CMD:=m} dump-jar tiny-framework-dump-test
# Get the path to the generate text files. (not the golden files.)
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index 88fa492addb8..c35d6d106c8d 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -28,8 +28,11 @@ GOLDEN_DIRS = [
# Run diff.
def run_diff(file1, file2):
+ # TODO(b/388562869) We shouldn't need `--ignore-matching-lines`, but the golden files may not have the "Constant pool:" lines.
command = ['diff', '-u', '--ignore-blank-lines',
- '--ignore-space-change', file1, file2]
+ '--ignore-space-change',
+ '--ignore-matching-lines=^\(Constant.pool:\|{\)$',
+ file1, file2]
print(' '.join(command))
result = subprocess.run(command, stderr=sys.stdout)
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6cd1f721d215..8e037c3ba90c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -542,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);
@@ -571,8 +572,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mVisibleBgUserIds = null;
mInputManager = context.getSystemService(InputManager.class);
if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
- mHearingDeviceNotificationController = new HearingDevicePhoneCallNotificationController(
- context);
+ mHearingDeviceNotificationController = hearingDeviceNotificationController;
} else {
mHearingDeviceNotificationController = null;
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3f6ede95eaf9..8804faf2d312 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -22,9 +22,9 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
+import android.app.backup.BackupManagerInternal;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
@@ -60,6 +60,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.backup.utils.RandomAccessFileUtils;
@@ -91,7 +92,7 @@ import java.util.Set;
* privileged callers (currently {@link DevicePolicyManager}). If called on {@link
* UserHandle#USER_SYSTEM}, backup is disabled for all users.
*/
-public class BackupManagerService extends IBackupManager.Stub {
+public class BackupManagerService extends IBackupManager.Stub implements BackupManagerInternal {
public static final String TAG = "BackupManagerService";
public static final boolean DEBUG = true;
public static final boolean MORE_DEBUG = false;
@@ -191,7 +192,6 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
- // TODO: Remove this when we implement DI by injecting in the construtor.
@VisibleForTesting
Handler getBackupHandler() {
return mHandler;
@@ -637,51 +637,28 @@ public class BackupManagerService extends IBackupManager.Stub {
}
@Override
- public void agentConnectedForUser(int userId, String packageName, IBinder agent)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- agentConnected(userId, packageName, agent);
+ public void agentConnectedForUser(String packageName, @UserIdInt int userId, IBinder agent) {
+ if (!isUserReadyForBackup(userId)) {
+ return;
}
- }
- @Override
- public void agentConnected(String packageName, IBinder agent) throws RemoteException {
- agentConnectedForUser(binderGetCallingUserId(), packageName, agent);
- }
-
- /**
- * Callback: a requested backup agent has been instantiated. This should only be called from the
- * {@link ActivityManager}.
- */
- public void agentConnected(@UserIdInt int userId, String packageName, IBinder agentBinder) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "agentConnected()");
+ UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission(
+ userId, "agentConnected()");
if (userBackupManagerService != null) {
userBackupManagerService.getBackupAgentConnectionManager().agentConnected(packageName,
- agentBinder);
+ agent);
}
}
@Override
- public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- agentDisconnected(userId, packageName);
+ public void agentDisconnectedForUser(String packageName, @UserIdInt int userId) {
+ if (!isUserReadyForBackup(userId)) {
+ return;
}
- }
- @Override
- public void agentDisconnected(String packageName) throws RemoteException {
- agentDisconnectedForUser(binderGetCallingUserId(), packageName);
- }
-
- /**
- * Callback: a backup agent has failed to come up, or has unexpectedly quit. This should only be
- * called from the {@link ActivityManager}.
- */
- public void agentDisconnected(@UserIdInt int userId, String packageName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()");
+ UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission(
+ userId, "agentDisconnected()");
if (userBackupManagerService != null) {
userBackupManagerService.getBackupAgentConnectionManager().agentDisconnected(
@@ -1688,7 +1665,7 @@ public class BackupManagerService extends IBackupManager.Stub {
* @param userId User id on which the backup operation is being requested.
* @param message A message to include in the exception if it is thrown.
*/
- void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) {
+ private void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) {
if (binderGetCallingUserId() != userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
@@ -1697,6 +1674,8 @@ public class BackupManagerService extends IBackupManager.Stub {
/** Implementation to receive lifecycle event callbacks for system services. */
public static class Lifecycle extends SystemService {
+ private final BackupManagerService mBackupManagerService;
+
public Lifecycle(Context context) {
this(context, new BackupManagerService(context));
}
@@ -1704,12 +1683,14 @@ public class BackupManagerService extends IBackupManager.Stub {
@VisibleForTesting
Lifecycle(Context context, BackupManagerService backupManagerService) {
super(context);
+ mBackupManagerService = backupManagerService;
sInstance = backupManagerService;
+ LocalServices.addService(BackupManagerInternal.class, mBackupManagerService);
}
@Override
public void onStart() {
- publishService(Context.BACKUP_SERVICE, BackupManagerService.sInstance);
+ publishService(Context.BACKUP_SERVICE, mBackupManagerService);
}
@Override
@@ -1717,17 +1698,17 @@ public class BackupManagerService extends IBackupManager.Stub {
// Starts the backup service for this user if backup is active for this user. Offloads
// work onto the handler thread {@link #mHandlerThread} to keep unlock time low since
// backup is not essential for device functioning.
- sInstance.postToHandler(
+ mBackupManagerService.postToHandler(
() -> {
- sInstance.updateDefaultBackupUserIdIfNeeded();
- sInstance.startServiceForUser(user.getUserIdentifier());
- sInstance.mHasFirstUserUnlockedSinceBoot = true;
+ mBackupManagerService.updateDefaultBackupUserIdIfNeeded();
+ mBackupManagerService.startServiceForUser(user.getUserIdentifier());
+ mBackupManagerService.mHasFirstUserUnlockedSinceBoot = true;
});
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
- sInstance.onStopUser(user.getUserIdentifier());
+ mBackupManagerService.onStopUser(user.getUserIdentifier());
}
@VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index aeb2f5e9be84..40726b4331e2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -767,7 +767,7 @@ public class VirtualDeviceManagerService extends SystemService {
params,
/* activityListener= */ null,
/* soundEffectListener= */ null);
- return new VirtualDeviceManager.VirtualDevice(mImpl, getContext(), virtualDevice);
+ return new VirtualDeviceManager.VirtualDevice(getContext(), virtualDevice);
}
@Override
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 31f6ef9fc062..1d914c89c570 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -729,8 +729,10 @@ public class BinaryTransparencyService extends SystemService {
private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
pw.println("--- Module Details ---");
pw.println("Module name: " + moduleInfo.getName());
- pw.println("Module visibility: "
- + (moduleInfo.isHidden() ? "hidden" : "visible"));
+ if (!android.content.pm.Flags.removeHiddenModuleUsage()) {
+ pw.println("Module visibility: "
+ + (moduleInfo.isHidden() ? "hidden" : "visible"));
+ }
}
private void printAppDetails(PackageInfo packageInfo,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b536dc524a80..d9c105c512fa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -256,7 +256,7 @@ import android.app.ServiceStartNotAllowedException;
import android.app.WaitResult;
import android.app.assist.ActivityId;
import android.app.backup.BackupAnnotations.BackupDestination;
-import android.app.backup.IBackupManager;
+import android.app.backup.BackupManagerInternal;
import android.app.compat.CompatChanges;
import android.app.job.JobParameters;
import android.app.usage.UsageEvents;
@@ -4490,11 +4490,8 @@ public class ActivityManagerService extends IActivityManager.Stub
final int userId = app.userId;
final String packageName = app.info.packageName;
mHandler.post(() -> {
- try {
- getBackupManager().agentDisconnectedForUser(userId, packageName);
- } catch (RemoteException e) {
- // Can't happen; the backup manager is local
- }
+ LocalServices.getService(BackupManagerInternal.class).agentDisconnectedForUser(
+ packageName, userId);
});
}
} else {
@@ -12864,6 +12861,28 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ final long kernelCmaUsage = Debug.getKernelCmaUsageKb();
+ if (kernelCmaUsage >= 0) {
+ pw.print(" Kernel CMA: ");
+ pw.println(stringifyKBSize(kernelCmaUsage));
+ // CMA memory can be in one of the following four states:
+ //
+ // 1. Free, in which case it is accounted for as part of MemFree, which
+ // is already considered in the lostRAM calculation below.
+ //
+ // 2. Allocated as part of a userspace allocated, in which case it is
+ // already accounted for in the total PSS value that was computed.
+ //
+ // 3. Allocated for storing compressed memory (ZRAM) on Android kernels.
+ // This is accounted for by calculating the amount of memory ZRAM
+ // consumes and including it in the lostRAM calculuation.
+ //
+ // 4. Allocated by a kernel driver, in which case, it is currently not
+ // attributed to any term that has been derived thus far. Since the
+ // allocations come from a kernel driver, add it to kernelUsed.
+ kernelUsed += kernelCmaUsage;
+ }
+
// Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
// memInfo.getCachedSizeKb().
final long lostRAM = memInfo.getTotalSizeKb()
@@ -13381,12 +13400,32 @@ public class ActivityManagerService extends IActivityManager.Stub
proto.write(MemInfoDumpProto.CACHED_KERNEL_KB, memInfo.getCachedSizeKb());
proto.write(MemInfoDumpProto.FREE_KB, memInfo.getFreeSizeKb());
}
+ // CMA memory can be in one of the following four states:
+ //
+ // 1. Free, in which case it is accounted for as part of MemFree, which
+ // is already considered in the lostRAM calculation below.
+ //
+ // 2. Allocated as part of a userspace allocated, in which case it is
+ // already accounted for in the total PSS value that was computed.
+ //
+ // 3. Allocated for storing compressed memory (ZRAM) on Android Kernels.
+ // This is accounted for by calculating hte amount of memory ZRAM
+ // consumes and including it in the lostRAM calculation.
+ //
+ // 4. Allocated by a kernel driver, in which case, it is currently not
+ // attributed to any term that has been derived thus far, so subtract
+ // it from lostRAM.
+ long kernelCmaUsage = Debug.getKernelCmaUsageKb();
+ if (kernelCmaUsage < 0) {
+ kernelCmaUsage = 0;
+ }
long lostRAM = memInfo.getTotalSizeKb()
- (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS])
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
// NR_SHMEM is subtracted twice (getCachedSizeKb() and getKernelUsedSizeKb())
+ memInfo.getShmemSizeKb()
- - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
+ - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb()
+ - kernelCmaUsage;
proto.write(MemInfoDumpProto.USED_PSS_KB, ss[INDEX_TOTAL_PSS] - cachedPss);
proto.write(MemInfoDumpProto.USED_KERNEL_KB, memInfo.getKernelUsedSizeKb());
proto.write(MemInfoDumpProto.LOST_RAM_KB, lostRAM);
@@ -13505,11 +13544,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
+ backupTarget.appInfo + " died during backup");
mHandler.post(() -> {
- try {
- getBackupManager().agentDisconnectedForUser(app.userId, app.info.packageName);
- } catch (RemoteException e) {
- // can't happen; backup manager is local
- }
+ LocalServices.getService(BackupManagerInternal.class).agentDisconnectedForUser(
+ app.info.packageName, app.userId);
});
}
@@ -14223,9 +14259,8 @@ public class ActivityManagerService extends IActivityManager.Stub
final long oldIdent = Binder.clearCallingIdentity();
try {
- getBackupManager().agentConnectedForUser(userId, agentPackageName, agent);
- } catch (RemoteException e) {
- // can't happen; the backup manager service is local
+ LocalServices.getService(BackupManagerInternal.class).agentConnectedForUser(
+ agentPackageName, userId, agent);
} catch (Exception e) {
Slog.w(TAG, "Exception trying to deliver BackupAgent binding: ");
e.printStackTrace();
@@ -16617,7 +16652,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void onUserRemoved(@UserIdInt int userId) {
+ public void onUserRemoving(@UserIdInt int userId) {
// Clean up any ActivityTaskManager state (by telling it the user is stopped)
mAtmInternal.onUserStopped(userId);
// Clean up various services by removing the user
@@ -16631,6 +16666,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public void onUserRemoved(int userId) {
+ // Clean up UserController state
+ mUserController.onUserRemoved(userId);
+ }
+
+ @Override
public boolean startUserInBackground(final int userId) {
return ActivityManagerService.this.startUserInBackground(userId);
}
@@ -19374,7 +19415,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (preventIntentRedirectCollectNestedKeysOnServerIfNotCollected()) {
// this flag will be ramped to public.
- intent.collectExtraIntentKeys();
+ intent.collectExtraIntentKeys(true);
}
}
@@ -19440,8 +19481,4 @@ public class ActivityManagerService extends IActivityManager.Stub
}
return token;
}
-
- private IBackupManager getBackupManager() {
- return IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
- }
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ec74f60539a2..d76c04ac7f31 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -125,7 +125,6 @@ import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ObjectUtils;
@@ -161,7 +160,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
-import java.util.function.Consumer;
/**
* Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -227,14 +225,6 @@ class UserController implements Handler.Callback {
private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
/**
- * Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
- * called after dismissing the keyguard.
- * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()}
- * and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
- */
- private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;
-
- /**
* Time after last scheduleOnUserCompletedEvent() call at which USER_COMPLETED_EVENT_MSG will be
* scheduled (although it may fire sooner instead).
* When it fires, {@link #reportOnUserCompletedEvent} will be processed.
@@ -455,11 +445,6 @@ class UserController implements Handler.Callback {
public void onUserCreated(UserInfo user, Object token) {
onUserAdded(user);
}
-
- @Override
- public void onUserRemoved(UserInfo user) {
- UserController.this.onUserRemoved(user.id);
- }
};
UserController(ActivityManagerService service) {
@@ -2010,7 +1995,7 @@ class UserController implements Handler.Callback {
mInjector.getWindowManager().setSwitchingUser(true);
// Only lock if the user has a secure keyguard PIN/Pattern/Pwd
if (mInjector.getKeyguardManager().isDeviceSecure(userId)) {
- // Make sure the device is locked before moving on with the user switch
+ Slogf.d(TAG, "Locking the device before moving on with the user switch");
mInjector.lockDeviceNowAndWaitForKeyguardShown();
}
}
@@ -2640,7 +2625,7 @@ class UserController implements Handler.Callback {
EventLog.writeEvent(EventLogTags.UC_CONTINUE_USER_SWITCH, oldUserId, newUserId);
- // Do the keyguard dismiss and dismiss the user switching dialog later
+ // Dismiss the user switching dialog and complete the user switch
mHandler.removeMessages(COMPLETE_USER_SWITCH_MSG);
mHandler.sendMessage(mHandler.obtainMessage(
COMPLETE_USER_SWITCH_MSG, oldUserId, newUserId));
@@ -2655,31 +2640,17 @@ class UserController implements Handler.Callback {
@VisibleForTesting
void completeUserSwitch(int oldUserId, int newUserId) {
- final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
- // serialize each conditional step
- await(
- // STEP 1 - If there is no challenge set, dismiss the keyguard right away
- isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId),
- mInjector::dismissKeyguard,
- () -> await(
- // STEP 2 - If user switch ui was enabled, dismiss user switch dialog
- isUserSwitchUiEnabled,
- this::dismissUserSwitchDialog,
- () -> {
- // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast
- // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete
- mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(
- REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
- }
- ));
- }
-
- private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) {
- if (condition) {
- conditionalStep.accept(nextStep);
+ final Runnable runnable = () -> {
+ // Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast ACTION_USER_SWITCHED and call
+ // onUserSwitchComplete on UserSwitchObservers.
+ mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
+ };
+ if (isUserSwitchUiEnabled()) {
+ dismissUserSwitchDialog(runnable);
} else {
- nextStep.run();
+ runnable.run();
}
}
@@ -3381,10 +3352,12 @@ class UserController implements Handler.Callback {
if (mUserProfileGroupIds.keyAt(i) == userId
|| mUserProfileGroupIds.valueAt(i) == userId) {
mUserProfileGroupIds.removeAt(i);
-
}
}
mCurrentProfileIds = ArrayUtils.removeInt(mCurrentProfileIds, userId);
+ mUserLru.remove((Integer) userId);
+ mStartedUsers.remove(userId);
+ updateStartedUserArrayLU();
}
}
@@ -4127,33 +4100,6 @@ class UserController implements Handler.Callback {
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
- protected void dismissKeyguard(Runnable runnable) {
- final AtomicBoolean isFirst = new AtomicBoolean(true);
- final Runnable runOnce = () -> {
- if (isFirst.getAndSet(false)) {
- runnable.run();
- }
- };
-
- mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS);
- getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() {
- @Override
- public void onDismissError() throws RemoteException {
- mHandler.post(runOnce);
- }
-
- @Override
- public void onDismissSucceeded() throws RemoteException {
- mHandler.post(runOnce);
- }
-
- @Override
- public void onDismissCancelled() throws RemoteException {
- mHandler.post(runOnce);
- }
- }, /* message= */ null);
- }
-
boolean isHeadlessSystemUserMode() {
return UserManager.isHeadlessSystemUserMode();
}
@@ -4178,6 +4124,7 @@ class UserController implements Handler.Callback {
void lockDeviceNowAndWaitForKeyguardShown() {
if (getWindowManager().isKeyguardLocked()) {
+ Slogf.w(TAG, "Not locking the device since the keyguard is already locked");
return;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3f6484f0f58e..b9b06701a11b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -47,6 +47,7 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.IAudioManagerNative.HardeningType;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.concurrentAudioRecordBypassPermission;
@@ -151,6 +152,7 @@ import android.media.BluetoothProfileConnectionInfo;
import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
+import android.media.IAudioManagerNative;
import android.media.IAudioModeDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IAudioServerStateDispatcher;
@@ -835,6 +837,18 @@ public class AudioService extends IAudioService.Stub
private final UserRestrictionsListener mUserRestrictionsListener =
new AudioServiceUserRestrictionsListener();
+ private final IAudioManagerNative mNativeShim = new IAudioManagerNative.Stub() {
+ // oneway
+ @Override
+ public void playbackHardeningEvent(int uid, byte type, boolean bypassed) {
+ }
+
+ @Override
+ public void permissionUpdateBarrier() {
+ AudioService.this.permissionUpdateBarrier();
+ }
+ };
+
// List of binder death handlers for setMode() client processes.
// The last process to have called setMode() is at the top of the list.
// package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers
@@ -2811,6 +2825,11 @@ public class AudioService extends IAudioService.Stub
args, callback, resultReceiver);
}
+ @Override
+ public IAudioManagerNative getNativeInterface() {
+ return mNativeShim;
+ }
+
/** @see AudioManager#getSurroundFormats() */
@Override
public Map<Integer, Boolean> getSurroundFormats() {
@@ -7214,7 +7233,7 @@ public class AudioService extends IAudioService.Stub
final int pid = Binder.getCallingPid();
final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
.append(") from u/pid:").append(uid).append("/")
- .append(pid).toString();
+ .append(pid).append(" src:AudioService.setBtA2dpOn").toString();
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+ MediaMetrics.SEPARATOR + "setBluetoothA2dpOn")
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 24296406da00..93fdbc787ed0 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -316,6 +316,31 @@ final class InputGestureManager {
}
}
+ @Nullable
+ public InputGestureData getInputGesture(int userId, InputGestureData.Trigger trigger) {
+ synchronized (mGestureLock) {
+ if (mBlockListedTriggers.contains(trigger)) {
+ return new InputGestureData.Builder().setTrigger(trigger).setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_RESERVED).build();
+ }
+ if (trigger instanceof InputGestureData.KeyTrigger keyTrigger) {
+ if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) ||
+ KeyEvent.isSystemKey(keyTrigger.getKeycode())) {
+ return new InputGestureData.Builder().setTrigger(trigger).setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_RESERVED).build();
+ }
+ }
+ InputGestureData gestureData = mSystemShortcuts.get(trigger);
+ if (gestureData != null) {
+ return gestureData;
+ }
+ if (!mCustomInputGestures.contains(userId)) {
+ return null;
+ }
+ return mCustomInputGestures.get(userId).get(trigger);
+ }
+ }
+
@InputManager.CustomInputGestureResult
public int addCustomInputGesture(int userId, InputGestureData newGesture) {
synchronized (mGestureLock) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index b2c35e1f362e..2ba35d6a70d2 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3061,6 +3061,16 @@ public class InputManagerService extends IInputManager.Stub
@Override
@PermissionManuallyEnforced
+ public AidlInputGestureData getInputGesture(@UserIdInt int userId,
+ @NonNull AidlInputGestureData.Trigger trigger) {
+ enforceManageKeyGesturePermission();
+
+ Objects.requireNonNull(trigger);
+ return mKeyGestureController.getInputGesture(userId, trigger);
+ }
+
+ @Override
+ @PermissionManuallyEnforced
public int addCustomInputGesture(@UserIdInt int userId,
@NonNull AidlInputGestureData inputGestureData) {
enforceManageKeyGesturePermission();
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 5f7ad2797368..fb5ce5b4e5fa 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -18,6 +18,8 @@ package com.android.server.input;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
@@ -55,7 +57,6 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.Display;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -64,6 +65,8 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IShortcutService;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.KeyCombinationManager;
import java.util.ArrayDeque;
@@ -159,6 +162,10 @@ final class KeyGestureController {
/** Currently fully consumed key codes per device */
private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
+ private final UserManagerInternal mUserManagerInternal;
+
+ private final boolean mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled();
+
KeyGestureController(Context context, Looper looper, InputDataStore inputDataStore) {
mContext = context;
mHandler = new Handler(looper, this::handleMessage);
@@ -180,6 +187,7 @@ final class KeyGestureController {
mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext);
mInputGestureManager = new InputGestureManager(mContext);
mInputDataStore = inputDataStore;
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
initBehaviors();
initKeyCombinationRules();
}
@@ -449,6 +457,9 @@ final class KeyGestureController {
}
public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+ if (mVisibleBackgroundUsersEnabled && shouldIgnoreKeyEventForVisibleBackgroundUser(event)) {
+ return false;
+ }
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
if (InputSettings.doesKeyGestureEventHandlerSupportMultiKeyGestures()
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
@@ -457,6 +468,24 @@ final class KeyGestureController {
return false;
}
+ private boolean shouldIgnoreKeyEventForVisibleBackgroundUser(KeyEvent event) {
+ final int displayAssignedUserId = mUserManagerInternal.getUserAssignedToDisplay(
+ event.getDisplayId());
+ final int currentUserId;
+ synchronized (mUserLock) {
+ currentUserId = mCurrentUserId;
+ }
+ if (currentUserId != displayAssignedUserId
+ && !KeyEvent.isVisibleBackgroundUserAllowedKey(event.getKeyCode())) {
+ if (DEBUG) {
+ Slog.w(TAG, "Ignored key event [" + event + "] for visible background user ["
+ + displayAssignedUserId + "]");
+ }
+ return true;
+ }
+ return false;
+ }
+
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
// TODO(b/358569822): Handle shortcuts trigger logic here and pass it to appropriate
@@ -895,7 +924,7 @@ final class KeyGestureController {
private void handleMultiKeyGesture(int[] keycodes,
@KeyGestureEvent.KeyGestureType int gestureType, int action, int flags) {
handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, keycodes, /* modifierState= */0,
- gestureType, action, Display.DEFAULT_DISPLAY, /* focusedToken = */null, flags,
+ gestureType, action, DEFAULT_DISPLAY, /* focusedToken = */null, flags,
/* appLaunchData = */null);
}
@@ -903,7 +932,7 @@ final class KeyGestureController {
@Nullable AppLaunchData appLaunchData) {
handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, new int[0], /* modifierState= */0,
keyGestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- Display.DEFAULT_DISPLAY, /* focusedToken = */null, /* flags = */0, appLaunchData);
+ DEFAULT_DISPLAY, /* focusedToken = */null, /* flags = */0, appLaunchData);
}
@VisibleForTesting
@@ -915,6 +944,11 @@ final class KeyGestureController {
}
private boolean handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) {
+ if (mVisibleBackgroundUsersEnabled && event.displayId != DEFAULT_DISPLAY
+ && shouldIgnoreGestureEventForVisibleBackgroundUser(event.gestureType,
+ event.displayId)) {
+ return false;
+ }
synchronized (mKeyGestureHandlerRecords) {
for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) {
if (handler.handleKeyGesture(event, focusedToken)) {
@@ -927,6 +961,24 @@ final class KeyGestureController {
return false;
}
+ private boolean shouldIgnoreGestureEventForVisibleBackgroundUser(
+ @KeyGestureEvent.KeyGestureType int gestureType, int displayId) {
+ final int displayAssignedUserId = mUserManagerInternal.getUserAssignedToDisplay(displayId);
+ final int currentUserId;
+ synchronized (mUserLock) {
+ currentUserId = mCurrentUserId;
+ }
+ if (currentUserId != displayAssignedUserId
+ && !KeyGestureEvent.isVisibleBackgrounduserAllowedGesture(gestureType)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Ignored gesture event [" + gestureType
+ + "] for visible background user [" + displayAssignedUserId + "]");
+ }
+ return true;
+ }
+ return false;
+ }
+
private boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) {
synchronized (mKeyGestureHandlerRecords) {
for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) {
@@ -943,7 +995,7 @@ final class KeyGestureController {
// TODO(b/358569822): Once we move the gesture detection logic to IMS, we ideally
// should not rely on PWM to tell us about the gesture start and end.
AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState,
- gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY,
+ gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, DEFAULT_DISPLAY,
/* flags = */0, /* appLaunchData = */null);
mHandler.obtainMessage(MSG_NOTIFY_KEY_GESTURE_EVENT, event).sendToTarget();
}
@@ -951,7 +1003,7 @@ final class KeyGestureController {
public void handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
@KeyGestureEvent.KeyGestureType int gestureType) {
AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState,
- gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY,
+ gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, DEFAULT_DISPLAY,
/* flags = */0, /* appLaunchData = */null);
handleKeyGesture(event, null /*focusedToken*/);
}
@@ -1069,6 +1121,18 @@ final class KeyGestureController {
}
@BinderThread
+ @Nullable
+ public AidlInputGestureData getInputGesture(@UserIdInt int userId,
+ @NonNull AidlInputGestureData.Trigger trigger) {
+ InputGestureData gestureData = mInputGestureManager.getInputGesture(userId,
+ InputGestureData.createTriggerFromAidlTrigger(trigger));
+ if (gestureData == null) {
+ return null;
+ }
+ return gestureData.getAidlData();
+ }
+
+ @BinderThread
@InputManager.CustomInputGestureResult
public int addCustomInputGesture(@UserIdInt int userId,
@NonNull AidlInputGestureData inputGestureData) {
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index b0dff22c6f03..281db0ae9518 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -387,8 +387,9 @@ public final class ImeVisibilityStateComputer {
@GuardedBy("ImfLock.class")
void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
- if (state != null && newState.hasEditorFocused()
- && newState.mToolType != MotionEvent.TOOL_TYPE_STYLUS) {
+ if (state != null && newState.hasEditorFocused() && (
+ newState.mToolType != MotionEvent.TOOL_TYPE_STYLUS
+ || Flags.refactorInsetsController())) {
// Inherit the last requested IME visible state when the target window is still
// focused with an editor.
newState.setRequestedImeVisible(state.mRequestedImeVisible);
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..1e54beeb2d64 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
@@ -227,6 +248,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
}
mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
+ releaseWakeLockOnExit();
}
@Override
@@ -302,6 +324,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 +386,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 +401,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,16 +416,30 @@ 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) {
+ byte code = onMessageReceivedInternal(sessionId, message);
+ if (code != ErrorCode.OK && message.isResponseRequired()) {
+ sendMessageDeliveryStatus(
+ sessionId, message.getMessageSequenceNumber(), code);
+ }
+ }
+
+ /* package */ void onMessageDeliveryStatusReceived(
+ int sessionId, int sequenceNumber, byte errorCode) {
+ mTransactionManager.onMessageDeliveryResponse(sequenceNumber, errorCode == ErrorCode.OK);
+ }
+
+ /* package */ boolean hasSessionId(int sessionId) {
+ synchronized (mOpenSessionLock) {
+ return mSessionInfoMap.contains(sessionId);
+ }
+ }
+
+ private byte onMessageReceivedInternal(int sessionId, HubMessage message) {
HubEndpointInfo remote;
synchronized (mOpenSessionLock) {
if (!isSessionActive(sessionId)) {
@@ -411,9 +449,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ sessionId
+ ") with message: "
+ message);
- sendMessageDeliveryStatus(
- sessionId, message.getMessageSequenceNumber(), ErrorCode.PERMANENT_ERROR);
- return;
+ return ErrorCode.PERMANENT_ERROR;
}
remote = mSessionInfoMap.get(sessionId).getRemoteEndpointInfo();
}
@@ -435,31 +471,12 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ ". "
+ mPackageName
+ " doesn't have permission");
- sendMessageDeliveryStatus(
- sessionId, message.getMessageSequenceNumber(), ErrorCode.PERMISSION_DENIED);
- 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);
- }
+ return ErrorCode.PERMISSION_DENIED;
}
- }
-
- /* package */ void onMessageDeliveryStatusReceived(
- int sessionId, int sequenceNumber, byte errorCode) {
- mTransactionManager.onMessageDeliveryResponse(sequenceNumber, errorCode == ErrorCode.OK);
- }
- /* package */ boolean hasSessionId(int sessionId) {
- synchronized (mOpenSessionLock) {
- return mSessionInfoMap.contains(sessionId);
- }
+ boolean success =
+ invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message));
+ return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR;
}
/**
@@ -520,4 +537,63 @@ 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);
+ }
+ }
+ });
+ }
+
+ private void releaseWakeLockOnExit() {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ while (mWakeLock.isHeld()) {
+ try {
+ mWakeLock.release();
+ } catch (RuntimeException e) {
+ Log.e(
+ TAG,
+ "Releasing the wakelock for all acquisitions fails - ",
+ e);
+ break;
+ }
+ }
+ });
+ }
+
+ /**
+ * 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/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 27577641ad1d..28e21b71dcc9 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -302,6 +302,15 @@ public class LocationFudger {
// requires latitude since longitudinal distances change with distance from equator.
private static double metersToDegreesLongitude(double distance, double lat) {
- return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / Math.cos(Math.toRadians(lat));
+ // Needed to convert from longitude distance to longitude degree.
+ // X meters near the poles is more degrees than at the equator.
+ double cosLat = Math.cos(Math.toRadians(lat));
+ // If we are right on top of the pole, the degree is always 0.
+ // We return a very small value instead to avoid divide by zero errors
+ // later on.
+ if (cosLat == 0.0) {
+ return 0.0001;
+ }
+ return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / cosLat;
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 286238e7888c..0d0cdd83cc73 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -438,9 +438,9 @@ public class LockSettingsService extends ILockSettings.Stub {
}
LockscreenCredential credential =
LockscreenCredential.createUnifiedProfilePassword(newPassword);
- Arrays.fill(newPasswordChars, '\u0000');
- Arrays.fill(newPassword, (byte) 0);
- Arrays.fill(randomLockSeed, (byte) 0);
+ LockPatternUtils.zeroize(newPasswordChars);
+ LockPatternUtils.zeroize(newPassword);
+ LockPatternUtils.zeroize(randomLockSeed);
return credential;
}
@@ -1537,7 +1537,7 @@ public class LockSettingsService extends ILockSettings.Stub {
+ userId);
}
} finally {
- Arrays.fill(password, (byte) 0);
+ LockPatternUtils.zeroize(password);
}
}
@@ -1570,7 +1570,7 @@ public class LockSettingsService extends ILockSettings.Stub {
decryptionResult = cipher.doFinal(encryptedPassword);
LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword(
decryptionResult);
- Arrays.fill(decryptionResult, (byte) 0);
+ LockPatternUtils.zeroize(decryptionResult);
try {
long parentSid = getGateKeeperService().getSecureUserId(
mUserManager.getProfileParent(userId).id);
@@ -2263,7 +2263,7 @@ public class LockSettingsService extends ILockSettings.Stub {
} catch (RemoteException e) {
Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
} finally {
- Arrays.fill(secret, (byte) 0);
+ LockPatternUtils.zeroize(secret);
}
}
diff --git a/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
index 21caf76d30d0..3d64f1890073 100644
--- a/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
@@ -26,6 +26,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import java.security.GeneralSecurityException;
@@ -154,7 +155,7 @@ public class UnifiedProfilePasswordCache {
}
LockscreenCredential result =
LockscreenCredential.createUnifiedProfilePassword(credential);
- Arrays.fill(credential, (byte) 0);
+ LockPatternUtils.zeroize(credential);
return result;
}
}
@@ -175,7 +176,7 @@ public class UnifiedProfilePasswordCache {
Slog.d(TAG, "Cannot delete key", e);
}
if (mEncryptedPasswords.contains(userId)) {
- Arrays.fill(mEncryptedPasswords.get(userId), (byte) 0);
+ LockPatternUtils.zeroize(mEncryptedPasswords.get(userId));
mEncryptedPasswords.remove(userId);
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index bf1b3c3f0b35..85dc811a7811 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -162,7 +162,7 @@ public class KeySyncTask implements Runnable {
Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
} finally {
if (mCredential != null) {
- Arrays.fill(mCredential, (byte) 0); // no longer needed.
+ LockPatternUtils.zeroize(mCredential); // no longer needed.
}
}
}
@@ -506,7 +506,7 @@ public class KeySyncTask implements Runnable {
try {
byte[] hash = MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
- Arrays.fill(bytes, (byte) 0);
+ LockPatternUtils.zeroize(bytes);
return hash;
} catch (NoSuchAlgorithmException e) {
// Impossible, SHA-256 must be supported on Android.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 54303c01890a..7d8300a8148a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -1082,7 +1082,7 @@ public class RecoverableKeyStoreManager {
int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType);
try (LockscreenCredential credential =
createLockscreenCredential(keyguardCredentialsType, decryptedCredentials)) {
- Arrays.fill(decryptedCredentials, (byte) 0);
+ LockPatternUtils.zeroize(decryptedCredentials);
decryptedCredentials = null;
VerifyCredentialResponse verifyResponse =
lockSettingsService.verifyCredential(credential, userId, 0);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
index 0e66746f4160..f1ef333d223a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -19,8 +19,9 @@ package com.android.server.locksettings.recoverablekeystore.storage;
import android.annotation.Nullable;
import android.util.SparseArray;
+import com.android.internal.widget.LockPatternUtils;
+
import java.util.ArrayList;
-import java.util.Arrays;
import javax.security.auth.Destroyable;
@@ -187,8 +188,8 @@ public class RecoverySessionStorage implements Destroyable {
*/
@Override
public void destroy() {
- Arrays.fill(mLskfHash, (byte) 0);
- Arrays.fill(mKeyClaimant, (byte) 0);
+ LockPatternUtils.zeroize(mLskfHash);
+ LockPatternUtils.zeroize(mKeyClaimant);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 68e195d7f079..35bb19943a24 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -302,7 +302,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
final long token = Binder.clearCallingIdentity();
try {
- mAudioService.setBluetoothA2dpOn(on);
+ if (!Flags.disableSetBluetoothAd2pOnCalls()) {
+ mAudioService.setBluetoothA2dpOn(on);
+ }
} catch (RemoteException ex) {
Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn. on=" + on);
} finally {
@@ -677,7 +679,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
if (DEBUG) {
Slog.d(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
}
- mAudioService.setBluetoothA2dpOn(a2dpOn);
+ if (!Flags.disableSetBluetoothAd2pOnCalls()) {
+ mAudioService.setBluetoothA2dpOn(a2dpOn);
+ }
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
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/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 86fc732e9d04..d440d3ab3521 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.hardware.tv.mediaquality.IMediaQuality;
import android.media.quality.AmbientBacklightSettings;
import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IMediaQualityManager;
@@ -35,9 +36,11 @@ import android.media.quality.SoundProfile;
import android.media.quality.SoundProfileHandle;
import android.os.Binder;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@@ -45,6 +48,7 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.server.SystemService;
+import com.android.server.utils.Slogf;
import org.json.JSONException;
import org.json.JSONObject;
@@ -74,6 +78,7 @@ public class MediaQualityService extends SystemService {
private final BiMap<Long, String> mSoundProfileTempIdMap;
private final PackageManager mPackageManager;
private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ private IMediaQuality mMediaQuality;
public MediaQualityService(Context context) {
super(context);
@@ -88,6 +93,12 @@ public class MediaQualityService extends SystemService {
@Override
public void onStart() {
+ IBinder binder = ServiceManager.getService(IMediaQuality.DESCRIPTOR + "/default");
+ if (binder != null) {
+ Slogf.d(TAG, "binder is not null");
+ mMediaQuality = IMediaQuality.Stub.asInterface(binder);
+ }
+
publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService());
}
@@ -809,10 +820,29 @@ public class MediaQualityService extends SystemService {
if (!hasGlobalPictureQualityServicePermission()) {
//TODO: error handling
}
+
+ try {
+ if (mMediaQuality != null) {
+ mMediaQuality.setAutoPqEnabled(enabled);
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set auto picture quality", e);
+ }
}
@Override
public boolean isAutoPictureQualityEnabled(UserHandle user) {
+ try {
+ if (mMediaQuality != null) {
+ return mMediaQuality.getAutoPqEnabled();
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get auto picture quality", e);
+ }
return false;
}
@@ -821,10 +851,29 @@ public class MediaQualityService extends SystemService {
if (!hasGlobalPictureQualityServicePermission()) {
//TODO: error handling
}
+
+ try {
+ if (mMediaQuality != null) {
+ mMediaQuality.setAutoSrEnabled(enabled);
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set auto super resolution", e);
+ }
}
@Override
public boolean isSuperResolutionEnabled(UserHandle user) {
+ try {
+ if (mMediaQuality != null) {
+ return mMediaQuality.getAutoSrEnabled();
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get auto super resolution", e);
+ }
return false;
}
@@ -833,10 +882,29 @@ public class MediaQualityService extends SystemService {
if (!hasGlobalSoundQualityServicePermission()) {
//TODO: error handling
}
+
+ try {
+ if (mMediaQuality != null) {
+ mMediaQuality.setAutoAqEnabled(enabled);
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set auto audio quality", e);
+ }
}
@Override
public boolean isAutoSoundQualityEnabled(UserHandle user) {
+ try {
+ if (mMediaQuality != null) {
+ return mMediaQuality.getAutoAqEnabled();
+ }
+ } catch (UnsupportedOperationException e) {
+ Slog.e(TAG, "The current device is not supported");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get auto audio quality", e);
+ }
return false;
}
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 4b41696a4390..e47f8ae9d3a5 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -583,6 +583,15 @@ public class GroupHelper {
final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(
record.getUserId(), pkgName, sectioner);
+ // The notification was part of a different section => trigger regrouping
+ final FullyQualifiedGroupKey prevSectionKey = getPreviousValidSectionKey(record);
+ if (prevSectionKey != null && !fullAggregateGroupKey.equals(prevSectionKey)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Section changed for: " + record);
+ }
+ maybeUngroupOnSectionChanged(record, prevSectionKey);
+ }
+
// This notification is already aggregated
if (record.getGroupKey().equals(fullAggregateGroupKey.toString())) {
return false;
@@ -652,10 +661,33 @@ public class GroupHelper {
}
/**
+ * A notification was added that was previously part of a different section and needs to trigger
+ * GH state cleanup.
+ */
+ private void maybeUngroupOnSectionChanged(NotificationRecord record,
+ FullyQualifiedGroupKey prevSectionKey) {
+ maybeUngroupWithSections(record, prevSectionKey);
+ if (record.getGroupKey().equals(prevSectionKey.toString())) {
+ record.setOverrideGroupKey(null);
+ }
+ }
+
+ /**
* A notification was added that is app-grouped.
*/
private void maybeUngroupOnAppGrouped(NotificationRecord record) {
- maybeUngroupWithSections(record, getSectionGroupKeyWithFallback(record));
+ FullyQualifiedGroupKey currentSectionKey = getSectionGroupKeyWithFallback(record);
+
+ // The notification was part of a different section => trigger regrouping
+ final FullyQualifiedGroupKey prevSectionKey = getPreviousValidSectionKey(record);
+ if (prevSectionKey != null && !prevSectionKey.equals(currentSectionKey)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Section changed for: " + record);
+ }
+ currentSectionKey = prevSectionKey;
+ }
+
+ maybeUngroupWithSections(record, currentSectionKey);
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 7cbbe2938fd5..5a425057ea89 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -107,4 +107,9 @@ public interface NotificationDelegate {
* @param key the notification key
*/
void unbundleNotification(String key);
+ /**
+ * Called when the notification should be rebundled.
+ * @param key the notification key
+ */
+ void rebundleNotification(String key);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dd9741ce9ca1..341038f878d9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1888,6 +1888,36 @@ public class NotificationManagerService extends SystemService {
}
}
}
+
+ @Override
+ public void rebundleNotification(String key) {
+ if (!(notificationClassification() && notificationRegroupOnClassification())) {
+ return;
+ }
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) {
+ return;
+ }
+
+ if (DBG) {
+ Slog.v(TAG, "rebundleNotification: " + r);
+ }
+
+ if (r.getBundleType() != Adjustment.TYPE_OTHER) {
+ final Bundle classifBundle = new Bundle();
+ classifBundle.putInt(KEY_TYPE, r.getBundleType());
+ Adjustment adj = new Adjustment(r.getSbn().getPackageName(), r.getKey(),
+ classifBundle, "rebundle", r.getUserId());
+ applyAdjustmentLocked(r, adj, /* isPosted= */ true);
+ mRankingHandler.requestSort();
+ } else {
+ if (DBG) {
+ Slog.w(TAG, "Can't rebundle. No valid bundle type for: " + r);
+ }
+ }
+ }
+ }
};
NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
@@ -7134,6 +7164,7 @@ public class NotificationManagerService extends SystemService {
adjustments.putParcelable(KEY_TYPE, newChannel);
logClassificationChannelAdjustmentReceived(r, isPosted, classification);
+ r.setBundleType(classification);
}
}
r.addAdjustment(adjustment);
@@ -9537,7 +9568,8 @@ public class NotificationManagerService extends SystemService {
|| !Objects.equals(oldSbn.getNotification().getGroup(),
n.getNotification().getGroup())
|| oldSbn.getNotification().flags
- != n.getNotification().flags) {
+ != n.getNotification().flags
+ || !old.getChannel().getId().equals(r.getChannel().getId())) {
synchronized (mNotificationLock) {
final String autogroupName =
notificationForceGrouping() ?
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 93f512bc7e17..81af0d8a6d80 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;
@@ -226,6 +222,9 @@ public final class NotificationRecord {
// lifetime extended.
private boolean mCanceledAfterLifetimeExtension = false;
+ // type of the bundle if the notification was classified
+ private @Adjustment.Types int mBundleType = Adjustment.TYPE_OTHER;
+
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel) {
this.sbn = sbn;
@@ -471,6 +470,10 @@ public final class NotificationRecord {
}
}
+ if (android.service.notification.Flags.notificationClassification()) {
+ mBundleType = previous.mBundleType;
+ }
+
// Don't copy importance information or mGlobalSortKey, recompute them.
}
@@ -1493,23 +1496,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 +1519,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)) {
@@ -1689,6 +1636,14 @@ public final class NotificationRecord {
mCanceledAfterLifetimeExtension = canceledAfterLifetimeExtension;
}
+ public @Adjustment.Types int getBundleType() {
+ return mBundleType;
+ }
+
+ public void setBundleType(@Adjustment.Types int bundleType) {
+ mBundleType = bundleType;
+ }
+
/**
* Whether this notification is a conversation notification.
*/
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 9d25d18df87c..36eabae69b22 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1187,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);
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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4c70d2347fb7..a0fbc008475c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -125,7 +125,6 @@ import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
-import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.net.Uri;
@@ -171,7 +170,6 @@ import com.android.internal.pm.pkg.component.ParsedIntentInfo;
import com.android.internal.pm.pkg.component.ParsedPermission;
import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.EventLogTags;
@@ -186,7 +184,6 @@ import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedLibraryWrapper;
import com.android.server.rollback.RollbackManagerInternal;
-import com.android.server.security.FileIntegrityService;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedLongSparseArray;
@@ -195,7 +192,6 @@ import dalvik.system.VMRuntime;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
-import java.security.DigestException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -1165,11 +1161,8 @@ final class InstallPackageHelper {
}
try {
doRenameLI(request, parsedPackage);
- setUpFsVerity(parsedPackage);
- } catch (Installer.InstallerException | IOException | DigestException
- | NoSuchAlgorithmException | PrepareFailure e) {
- request.setError(PackageManagerException.INTERNAL_ERROR_VERITY_SETUP,
- "Failed to set up verity: " + e);
+ } catch (PrepareFailure e) {
+ request.setError(e);
return false;
}
@@ -2322,68 +2315,6 @@ final class InstallPackageHelper {
}
}
- /**
- * Set up fs-verity for the given package. For older devices that do not support fs-verity,
- * this is a no-op.
- */
- private void setUpFsVerity(AndroidPackage pkg) throws Installer.InstallerException,
- PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
- if (!PackageManagerServiceUtils.isApkVerityEnabled()) {
- return;
- }
-
- if (isIncrementalPath(pkg.getPath()) && IncrementalManager.getVersion()
- < IncrementalManager.MIN_VERSION_TO_SUPPORT_FSVERITY) {
- return;
- }
-
- // Collect files we care for fs-verity setup.
- ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
- fsverityCandidates.put(pkg.getBaseApkPath(),
- VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
-
- final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
- pkg.getBaseApkPath());
- if (new File(dmPath).exists()) {
- fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
- }
-
- for (String path : pkg.getSplitCodePaths()) {
- fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
-
- final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
- if (new File(splitDmPath).exists()) {
- fsverityCandidates.put(splitDmPath,
- VerityUtils.getFsveritySignatureFilePath(splitDmPath));
- }
- }
-
- var fis = FileIntegrityService.getService();
- for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
- try {
- final String filePath = entry.getKey();
- if (VerityUtils.hasFsverity(filePath)) {
- continue;
- }
-
- final String signaturePath = entry.getValue();
- if (new File(signaturePath).exists()) {
- // If signature is provided, enable fs-verity first so that the file can be
- // measured for signature check below.
- VerityUtils.setUpFsverity(filePath);
-
- if (!fis.verifyPkcs7DetachedSignature(signaturePath, filePath)) {
- throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
- "fs-verity signature does not verify against a known key");
- }
- }
- } catch (IOException e) {
- throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
- "Failed to enable fs-verity: " + e);
- }
- }
- }
-
private PackageFreezer freezePackageForInstall(String packageName, int userId, int installFlags,
String killReason, int exitInfoReason, InstallRequest request) {
if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
@@ -3060,6 +2991,8 @@ final class InstallPackageHelper {
}
if (succeeded) {
+ Slog.i(TAG, "installation completed:" + packageName);
+
if (Flags.aslInApkAppMetadataSource()
&& pkgSetting.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
if (!extractAppMetadataFromApk(request.getPkg(),
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index c6760431116e..1b41c3617a05 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -26,7 +26,6 @@ import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
import static android.content.pm.PackageItemInfo.MAX_SAFE_LABEL_LENGTH;
import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
-import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
@@ -824,8 +823,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private File mInheritedFilesBase;
- @GuardedBy("mLock")
- private boolean mVerityFoundForApks;
/**
* Both flags should be guarded with mLock whenever changes need to be in lockstep.
@@ -864,7 +861,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
} else {
if (DexMetadataHelper.isDexMetadataFile(file)) return false;
}
- if (VerityUtils.isFsveritySignatureFile(file)) return false;
if (ApkChecksums.isDigestOrDigestSignatureFile(file)) return false;
return true;
}
@@ -3565,13 +3561,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"Missing existing base package");
}
- // Default to require only if existing base apk has fs-verity signature.
- mVerityFoundForApks = PackageManagerServiceUtils.isApkVerityEnabled()
- && params.mode == SessionParams.MODE_INHERIT_EXISTING
- && VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath())
- && (new File(VerityUtils.getFsveritySignatureFilePath(
- pkgInfo.applicationInfo.getBaseCodePath()))).exists();
-
final List<File> removedFiles = getRemovedFilesLocked();
final List<String> removeSplitList = new ArrayList<>();
if (!removedFiles.isEmpty()) {
@@ -3972,24 +3961,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@GuardedBy("mLock")
- private void maybeStageFsveritySignatureLocked(File origFile, File targetFile,
- boolean fsVerityRequired) throws PackageManagerException {
- if (android.security.Flags.deprecateFsvSig()) {
- return;
- }
- final File originalSignature = new File(
- VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
- if (originalSignature.exists()) {
- final File stagedSignature = new File(
- VerityUtils.getFsveritySignatureFilePath(targetFile.getPath()));
- stageFileLocked(originalSignature, stagedSignature);
- } else if (fsVerityRequired) {
- throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
- "Missing corresponding fs-verity signature to " + origFile);
- }
- }
-
- @GuardedBy("mLock")
private void maybeStageV4SignatureLocked(File origFile, File targetFile)
throws PackageManagerException {
final File originalSignature = new File(origFile.getPath() + V4Signature.EXT);
@@ -4015,11 +3986,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
DexMetadataHelper.buildDexMetadataPathForApk(targetFile.getName()));
stageFileLocked(dexMetadataFile, targetDexMetadataFile);
-
- // Also stage .dm.fsv_sig. .dm may be required to install with fs-verity signature on
- // supported on older devices.
- maybeStageFsveritySignatureLocked(dexMetadataFile, targetDexMetadataFile,
- DexMetadataHelper.isFsVerityRequired());
}
@FlaggedApi(com.android.art.flags.Flags.FLAG_ART_SERVICE_V3)
@@ -4105,44 +4071,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@GuardedBy("mLock")
- private boolean isFsVerityRequiredForApk(File origFile, File targetFile)
- throws PackageManagerException {
- if (mVerityFoundForApks) {
- return true;
- }
-
- // We haven't seen .fsv_sig for any APKs. Treat it as not required until we see one.
- final File originalSignature = new File(
- VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
- if (!originalSignature.exists()) {
- return false;
- }
- mVerityFoundForApks = true;
-
- // When a signature is found, also check any previous staged APKs since they also need to
- // have fs-verity signature consistently.
- for (File file : mResolvedStagedFiles) {
- if (!file.getName().endsWith(".apk")) {
- continue;
- }
- // Ignore the current targeting file.
- if (targetFile.getName().equals(file.getName())) {
- continue;
- }
- throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
- "Previously staged apk is missing fs-verity signature");
- }
- return true;
- }
-
- @GuardedBy("mLock")
private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName,
List<String> artManagedFilePaths) throws PackageManagerException {
stageFileLocked(origFile, targetFile);
- // Stage APK's fs-verity signature if present.
- maybeStageFsveritySignatureLocked(origFile, targetFile,
- isFsVerityRequiredForApk(origFile, targetFile));
// Stage APK's v4 signature if present, and fs-verity is supported.
if (android.security.Flags.extendVbChainToUpdatedApk()
&& VerityUtils.isFsVeritySupported()) {
@@ -4160,16 +4092,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@GuardedBy("mLock")
- private void maybeInheritFsveritySignatureLocked(File origFile) {
- // Inherit the fsverity signature file if present.
- final File fsveritySignatureFile = new File(
- VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
- if (fsveritySignatureFile.exists()) {
- mResolvedInheritedFiles.add(fsveritySignatureFile);
- }
- }
-
- @GuardedBy("mLock")
private void maybeInheritV4SignatureLocked(File origFile) {
// Inherit the v4 signature file if present.
final File v4SignatureFile = new File(origFile.getPath() + V4Signature.EXT);
@@ -4182,7 +4104,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private void inheritFileLocked(File origFile, List<String> artManagedFilePaths) {
mResolvedInheritedFiles.add(origFile);
- maybeInheritFsveritySignatureLocked(origFile);
if (android.security.Flags.extendVbChainToUpdatedApk()) {
maybeInheritV4SignatureLocked(origFile);
}
@@ -4193,13 +4114,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
artManagedFilePaths, origFile.getPath())) {
File artManagedFile = new File(path);
mResolvedInheritedFiles.add(artManagedFile);
- maybeInheritFsveritySignatureLocked(artManagedFile);
}
} else {
final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(origFile);
if (dexMetadataFile != null) {
mResolvedInheritedFiles.add(dexMetadataFile);
- maybeInheritFsveritySignatureLocked(dexMetadataFile);
}
}
// Inherit the digests if present.
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 7af39f74d0d6..3e376b6958ec 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -520,22 +520,6 @@ public class PackageManagerServiceUtils {
}
}
- /** Default is to not use fs-verity since it depends on kernel support. */
- private static final int FSVERITY_DISABLED = 0;
-
- /** Standard fs-verity. */
- private static final int FSVERITY_ENABLED = 2;
-
- /** Returns true if standard APK Verity is enabled. */
- static boolean isApkVerityEnabled() {
- if (android.security.Flags.deprecateFsvSig()) {
- return false;
- }
- return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R
- || SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED)
- == FSVERITY_ENABLED;
- }
-
/**
* Verifies that signatures match.
* @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8249d65868cd..81956fbb55e6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6661,7 +6661,7 @@ public class UserManagerService extends IUserManager.Stub {
+ userId);
}
new Thread(() -> {
- getActivityManagerInternal().onUserRemoved(userId);
+ getActivityManagerInternal().onUserRemoving(userId);
removeUserState(userId);
}).start();
}
@@ -6701,6 +6701,7 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mUsersLock) {
removeUserDataLU(userId);
mIsUserManaged.delete(userId);
+ getActivityManagerInternal().onUserRemoved(userId);
}
synchronized (mUserStates) {
mUserStates.delete(userId);
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index a6f2a3757dcb..1cf24fcd8594 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -20,7 +20,6 @@ import static android.os.Flags.adpfUseFmqChannel;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.power.hint.Flags.adpfSessionTag;
-import static com.android.server.power.hint.Flags.cpuHeadroomAffinityCheck;
import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
import static com.android.server.power.hint.Flags.resetOnForkEnabled;
@@ -1604,8 +1603,7 @@ public final class HintManagerService extends SystemService {
}
}
}
- if (cpuHeadroomAffinityCheck() && mCheckHeadroomAffinity
- && params.tids.length > 1) {
+ if (mCheckHeadroomAffinity && params.tids.length > 1) {
checkThreadAffinityForTids(params.tids);
}
halParams.tids = params.tids;
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
index 17739712d65a..a75d110e3cd1 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -88,5 +88,6 @@ public class ResourcesManagerShellCommand extends ShellCommand {
out.println(" Print this help text.");
out.println(" dump <PROCESS>");
out.println(" Dump the Resources objects in use as well as the history of Resources");
+
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4ed5f90f2852..a19a3422af06 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2199,6 +2199,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
});
}
+ /**
+ * Called when the notification should be rebundled.
+ * @param key the notification key
+ */
+ @Override
+ public void rebundleNotification(String key) {
+ enforceStatusBarService();
+ enforceValidCallingUser();
+ Binder.withCleanCallingIdentity(() -> {
+ mNotificationDelegate.rebundleNotification(key);
+ });
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3e6315635571..1fe61590a531 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -232,6 +232,7 @@ import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
+import static com.android.server.wm.StartingData.AFTER_TRANSITION_FINISH;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
@@ -2814,9 +2815,27 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
attachStartingSurfaceToAssociatedTask();
}
+ /**
+ * If the device is locked and the app does not request showWhenLocked,
+ * defer removing the starting window until the transition is complete.
+ * This prevents briefly appearing the app context and causing secure concern.
+ */
+ void deferStartingWindowRemovalForKeyguardUnoccluding() {
+ if (mStartingData.mRemoveAfterTransaction != AFTER_TRANSITION_FINISH
+ && isKeyguardLocked() && !canShowWhenLockedInner(this) && !isVisibleRequested()
+ && mTransitionController.inTransition(this)) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSITION_FINISH;
+ }
+ }
+
void removeStartingWindow() {
boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
+ if (mStartingData != null
+ && mStartingData.mRemoveAfterTransaction == AFTER_TRANSITION_FINISH) {
+ return;
+ }
+
if (transferSplashScreenIfNeeded()) {
return;
}
@@ -4261,7 +4280,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
void finishRelaunching() {
- mAppCompatController.getAppCompatOrientationOverrides()
+ mAppCompatController.getOrientationOverrides()
.setRelaunchingAfterRequestedOrientationChanged(false);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
@@ -4655,6 +4674,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
tStartingWindow.mToken = this;
tStartingWindow.mActivityRecord = this;
+ if (mStartingData.mRemoveAfterTransaction == AFTER_TRANSITION_FINISH) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
+ }
if (mStartingData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
// The removal of starting window should wait for window drawn of current
// activity.
@@ -8125,10 +8147,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (task != null && requestedOrientation == SCREEN_ORIENTATION_BEHIND) {
// We use Task here because we want to be consistent with what happens in
// multi-window mode where other tasks orientations are ignored.
- final ActivityRecord belowCandidate = task.getActivity(
- a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
- this /* boundary */, false /* includeBoundary */,
- true /* traverseTopToBottom */);
+ final ActivityRecord belowCandidate = task.getActivityBelowForDefiningOrientation(this);
if (belowCandidate != null) {
return belowCandidate.getRequestedConfigurationOrientation(forDisplay);
}
@@ -8222,7 +8241,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)) {
@@ -10217,7 +10236,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 c748264a833e..6d0e8eacd438 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -89,8 +89,8 @@ class AppCompatController {
}
@NonNull
- AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
- return mAppCompatOverrides.getAppCompatOrientationOverrides();
+ AppCompatOrientationOverrides getOrientationOverrides() {
+ return mAppCompatOverrides.getOrientationOverrides();
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index 8866e39fecc9..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;
}
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 9effae6aa640..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
@@ -48,7 +48,7 @@ public class AppCompatOverrides {
@NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
- mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
+ mOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
appCompatConfiguration, appCompatDeviceStateQuery);
@@ -64,8 +64,8 @@ public class AppCompatOverrides {
}
@NonNull
- AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
- return mAppCompatOrientationOverrides;
+ AppCompatOrientationOverrides getOrientationOverrides() {
+ return mOrientationOverrides;
}
@NonNull
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/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index d49a507c9e11..5bec4424269a 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -25,6 +25,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.window.DisplayAreaOrganizer.FEATURE_APP_ZOOM_OUT;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
@@ -151,6 +153,12 @@ public abstract class DisplayAreaPolicy {
.all()
.except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
TYPE_SECURE_SYSTEM_OVERLAY)
+ .build())
+ .addFeature(new Feature.Builder(wmService.mPolicy, "AppZoomOut",
+ FEATURE_APP_ZOOM_OUT)
+ .all()
+ .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
+ TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE, TYPE_WALLPAPER)
.build());
}
rootHierarchy
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 145c7b37fcdc..d32c31f1c1c7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1822,7 +1822,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
private void applyFixedRotationForNonTopVisibleActivityIfNeeded(@NonNull ActivityRecord ar,
@ActivityInfo.ScreenOrientation int topOrientation) {
- final int orientation = ar.getRequestedOrientation();
+ int orientation = ar.getRequestedOrientation();
+ if (orientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+ final ActivityRecord nextCandidate = getActivityBelowForDefiningOrientation(ar);
+ if (nextCandidate != null) {
+ orientation = nextCandidate.getRequestedOrientation();
+ }
+ }
if (orientation == topOrientation || ar.inMultiWindowMode()
|| ar.getRequestedConfigurationOrientation() == ORIENTATION_UNDEFINED) {
return;
@@ -1864,9 +1870,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return ROTATION_UNDEFINED;
}
if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
- final ActivityRecord nextCandidate = getActivity(
- a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
- r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
+ final ActivityRecord nextCandidate = getActivityBelowForDefiningOrientation(r);
if (nextCandidate != null) {
r = nextCandidate;
activityOrientation = r.getOverrideOrientation();
@@ -2964,7 +2968,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"
@@ -4291,7 +4295,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return target;
}
if (android.view.inputmethod.Flags.refactorInsetsController()) {
- final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();
+ final DisplayContent defaultDc = getUserMainDisplayContent();
return defaultDc.mRemoteInsetsControlTarget;
} else {
return getImeFallback();
@@ -4301,11 +4305,26 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
InsetsControlTarget getImeFallback() {
// host is in non-default display that doesn't support system decor, default to
// default display's StatusBar to control IME (when available), else let system control it.
- final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();
- WindowState statusBar = defaultDc.getDisplayPolicy().getStatusBar();
+ final DisplayContent defaultDc = getUserMainDisplayContent();
+ final WindowState statusBar = defaultDc.getDisplayPolicy().getStatusBar();
return statusBar != null ? statusBar : defaultDc.mRemoteInsetsControlTarget;
}
+ private DisplayContent getUserMainDisplayContent() {
+ final DisplayContent defaultDc;
+ if (android.view.inputmethod.Flags.fallbackDisplayForSecondaryUserOnSecondaryDisplay()) {
+ final int userId = mWmService.mUmInternal.getUserAssignedToDisplay(mDisplayId);
+ defaultDc = mWmService.getUserMainDisplayContentLocked(userId);
+ if (defaultDc == null) {
+ throw new IllegalStateException(
+ "No default display was assigned to user " + userId);
+ }
+ } else {
+ defaultDc = mWmService.getDefaultDisplayContentLocked();
+ }
+ return defaultDc;
+ }
+
/**
* Returns the corresponding IME insets control target according the IME target type.
*
@@ -4841,8 +4860,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// The control target could be the RemoteInsetsControlTarget (if the focussed
// view is on a virtual display that can not show the IME (and therefore it will
// be shown on the default display)
- if (isDefaultDisplay && mRemoteInsetsControlTarget != null) {
- return mRemoteInsetsControlTarget;
+ if (android.view.inputmethod.Flags
+ .fallbackDisplayForSecondaryUserOnSecondaryDisplay()) {
+ if (isUserMainDisplay() && mRemoteInsetsControlTarget != null) {
+ return mRemoteInsetsControlTarget;
+ }
+ } else {
+ if (isDefaultDisplay && mRemoteInsetsControlTarget != null) {
+ return mRemoteInsetsControlTarget;
+ }
}
}
return null;
@@ -4858,6 +4884,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
+ * Returns {@code true} if {@link #mDisplayId} corresponds to the user's main display.
+ *
+ * <p>Visible background users may have other than DEFAULT_DISPLAY marked as their main display.
+ */
+ private boolean isUserMainDisplay() {
+ final int userId = mWmService.mUmInternal.getUserAssignedToDisplay(mDisplayId);
+ return mDisplayId == mWmService.mUmInternal.getMainDisplayAssignedToUser(userId);
+ }
+
+ /**
* Computes the window the IME should be attached to.
*/
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3a0e41a5f9f8..b3e9244d108d 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -45,6 +45,7 @@ import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Build;
@@ -70,6 +71,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.internal.view.IDragAndDropPermissions;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
@@ -542,10 +544,26 @@ class DragState {
}
}
ClipDescription description = data != null ? data.getDescription() : mDataDescription;
+
+ // Note this can be negative numbers if touch coords are left or top of the window.
+ PointF relativeToWindowCoords = new PointF(newWin.translateToWindowX(touchX),
+ newWin.translateToWindowY(touchY));
+ if (Flags.enableConnectedDisplaysDnd()
+ && mDisplayContent.getDisplayId() != newWin.getDisplayId()) {
+ // Currently DRAG_STARTED coords are sent relative to the window target in **px**
+ // coordinates. However, this cannot be extended to connected displays scenario,
+ // as there's only global **dp** coordinates and no global **px** coordinates.
+ // Hence, the coords sent here will only try to indicate that drag started outside
+ // this window display, but relative distance should not be calculated or depended
+ // on.
+ relativeToWindowCoords = new PointF(-newWin.getBounds().left - 1,
+ -newWin.getBounds().top - 1);
+ }
+
DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
- newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
- description, data, false /* includeDragSurface */,
- true /* includeDragFlags */, null /* dragAndDropPermission */);
+ relativeToWindowCoords.x, relativeToWindowCoords.y, description, data,
+ false /* includeDragSurface */, true /* includeDragFlags */,
+ null /* dragAndDropPermission */);
try {
newWin.mClient.dispatchDragEvent(event);
// track each window that we've notified that the drag is starting
diff --git a/services/core/java/com/android/server/wm/PersisterQueue.java b/services/core/java/com/android/server/wm/PersisterQueue.java
index 9dc3d6a81338..bc16a566bfef 100644
--- a/services/core/java/com/android/server/wm/PersisterQueue.java
+++ b/services/core/java/com/android/server/wm/PersisterQueue.java
@@ -86,6 +86,34 @@ class PersisterQueue {
mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
}
+ /**
+ * Busy wait until {@link #mLazyTaskWriterThread} is in {@link Thread.State#WAITING}, or
+ * times out. This indicates the thread is waiting for new tasks to appear. If the wait
+ * succeeds, this queue waits at least {@link #mPreTaskDelayMs} milliseconds before running the
+ * next task.
+ *
+ * <p>This is for testing purposes only.
+ *
+ * @param timeoutMillis the maximum time of waiting in milliseconds
+ * @return {@code true} if the thread is in {@link Thread.State#WAITING} at return
+ */
+ @VisibleForTesting
+ boolean waitUntilWritingThreadIsWaiting(long timeoutMillis) {
+ final long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis;
+ do {
+ Thread.State state;
+ synchronized (this) {
+ state = mLazyTaskWriterThread.getState();
+ }
+ if (state == Thread.State.WAITING) {
+ return true;
+ }
+ Thread.yield();
+ } while (SystemClock.uptimeMillis() < timeoutTime);
+
+ return false;
+ }
+
synchronized void startPersisting() {
if (!mLazyTaskWriterThread.isAlive()) {
mLazyTaskWriterThread.start();
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index a5454546341b..3eb13c52cca6 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -407,10 +407,8 @@ class SnapshotPersistQueue {
bitmap.recycle();
final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
- try {
- FileOutputStream fos = new FileOutputStream(file);
+ try (FileOutputStream fos = new FileOutputStream(file)) {
swBitmap.compress(JPEG, COMPRESS_QUALITY, fos);
- fos.close();
} catch (IOException e) {
Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
return false;
@@ -428,10 +426,8 @@ class SnapshotPersistQueue {
swBitmap.recycle();
final File lowResFile = mPersistInfoProvider.getLowResolutionBitmapFile(mId, mUserId);
- try {
- FileOutputStream lowResFos = new FileOutputStream(lowResFile);
+ try (FileOutputStream lowResFos = new FileOutputStream(lowResFile)) {
lowResBitmap.compress(JPEG, COMPRESS_QUALITY, lowResFos);
- lowResFos.close();
} catch (IOException e) {
Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
return false;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 7349224ddcd8..1a7a6196cf85 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -31,11 +31,18 @@ public abstract class StartingData {
static final int AFTER_TRANSACTION_REMOVE_DIRECTLY = 1;
/** Do copy splash screen to client after transaction done. */
static final int AFTER_TRANSACTION_COPY_TO_CLIENT = 2;
+ /**
+ * Remove the starting window after transition finish.
+ * Used when activity doesn't request show when locked, so the app window should never show to
+ * the user if device is locked.
+ **/
+ static final int AFTER_TRANSITION_FINISH = 3;
@IntDef(prefix = { "AFTER_TRANSACTION" }, value = {
AFTER_TRANSACTION_IDLE,
AFTER_TRANSACTION_REMOVE_DIRECTLY,
AFTER_TRANSACTION_COPY_TO_CLIENT,
+ AFTER_TRANSITION_FINISH,
})
@interface AfterTransaction {}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d92301ba4f6f..9c1cf6e6bf62 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5255,6 +5255,10 @@ class Task extends TaskFragment {
return false;
}
+ if (!mTaskSupervisor.readyToResume()) {
+ return false;
+ }
+
final ActivityRecord topActivity = topRunningActivity(true /* focusableOnly */);
if (topActivity == null) {
// There are no activities left in this task, let's look somewhere else.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a9646783b92d..f4a455a9c2dd 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -70,6 +70,8 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
+import static com.android.server.wm.StartingData.AFTER_TRANSITION_FINISH;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
@@ -1374,6 +1376,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
enterAutoPip = true;
}
}
+
+ if (ar.mStartingData != null && ar.mStartingData.mRemoveAfterTransaction
+ == AFTER_TRANSITION_FINISH
+ && (!ar.isVisible() || !ar.mTransitionController.inTransition(ar))) {
+ ar.mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
+ ar.removeStartingWindow();
+ }
final ChangeInfo changeInfo = mChanges.get(ar);
// Due to transient-hide, there may be some activities here which weren't in the
// transition.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index aa60f939f9aa..54a3d4179e3d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1659,6 +1659,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return ORIENTATION_UNDEFINED;
}
+ @Nullable
+ ActivityRecord getActivityBelowForDefiningOrientation(ActivityRecord from) {
+ return getActivity(ActivityRecord::canDefineOrientationForActivitiesAbove,
+ from /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
+ }
+
/**
* Calls {@link #setOrientation(int, WindowContainer)} with {@code null} to the last 2
* parameters.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 92e0931993d1..e4ef3d186bdb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7473,6 +7473,23 @@ public class WindowManagerService extends IWindowManager.Stub
return mRoot.getDisplayContent(DEFAULT_DISPLAY);
}
+ /**
+ * Returns the main display content for the user passed as parameter.
+ *
+ * <p>Visible background users may have their own designated main display, distinct from the
+ * system default display (DEFAULT_DISPLAY). Visible background users operate independently
+ * with their own main displays. These secondary user main displays host the secondary home
+ * activities.
+ */
+ @Nullable
+ DisplayContent getUserMainDisplayContentLocked(@UserIdInt int userId) {
+ final int userMainDisplayId = mUmInternal.getMainDisplayAssignedToUser(userId);
+ if (userMainDisplayId == -1) {
+ return null;
+ }
+ return mRoot.getDisplayContent(userMainDisplayId);
+ }
+
public void onOverlayChanged() {
// Post to display thread so it can get the latest display info.
mH.post(() -> {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b43e334d6e1a..d69b06ad71ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -126,6 +126,7 @@ import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.MoveAnimationSpecProto.DURATION_MS;
import static com.android.server.wm.MoveAnimationSpecProto.FROM;
import static com.android.server.wm.MoveAnimationSpecProto.TO;
+import static com.android.server.wm.StartingData.AFTER_TRANSITION_FINISH;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
@@ -1920,6 +1921,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
final ActivityRecord atoken = mActivityRecord;
if (atoken != null) {
+ if (atoken.mStartingData != null && mAttrs.type != TYPE_APPLICATION_STARTING
+ && atoken.mStartingData.mRemoveAfterTransaction
+ == AFTER_TRANSITION_FINISH) {
+ // Preventing app window from visible during un-occluding animation playing due to
+ // alpha blending.
+ return false;
+ }
final boolean isVisible = isStartingWindowAssociatedToTask()
? mStartingData.mAssociatedTask.isVisible() : atoken.isVisible();
return ((!isParentWindowHidden() && isVisible)
@@ -2925,7 +2933,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final int mask = FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD
| FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
WindowManager.LayoutParams sa = mActivityRecord.mStartingWindow.mAttrs;
+ final boolean wasShowWhenLocked = (sa.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
+ final boolean removeShowWhenLocked = (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) == 0;
sa.flags = (sa.flags & ~mask) | (mAttrs.flags & mask);
+ if (Flags.keepAppWindowHideWhileLocked() && wasShowWhenLocked && removeShowWhenLocked) {
+ // Trigger unoccluding animation if needed.
+ mActivityRecord.checkKeyguardFlagsChanged();
+ mActivityRecord.deferStartingWindowRemovalForKeyguardUnoccluding();
+ }
}
}
@@ -5424,7 +5439,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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e69a7414dd76..d2d388401e23 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8909,11 +8909,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (parent) {
Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId()));
+ isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()));
+ // If a DPC is querying on the parent instance, make sure it's only querying the parent
+ // user of itself. Querying any other user is not allowed.
+ Preconditions.checkArgument(caller.getUserId() == userHandle);
}
+ int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
Boolean disallowed = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.SCREEN_CAPTURE_DISABLED,
- userHandle);
+ affectedUserId);
return disallowed != null && disallowed;
}
@@ -14669,7 +14673,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled,
PersistableBundle options) {
- if (Flags.secondaryLockscreenApiEnabled()) {
+ if (Flags.secondaryLockscreenApiEnabled() && mSupervisionManagerInternal != null) {
final CallerIdentity caller = getCallerIdentity();
final boolean isRoleHolder = isCallerSystemSupervisionRoleHolder(caller);
synchronized (getLockObject()) {
@@ -14680,16 +14684,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getUserId());
}
- if (mSupervisionManagerInternal != null) {
- mSupervisionManagerInternal.setSupervisionLockscreenEnabledForUser(
- caller.getUserId(), enabled, options);
- } else {
- synchronized (getLockObject()) {
- DevicePolicyData policy = getUserData(caller.getUserId());
- policy.mSecondaryLockscreenEnabled = enabled;
- saveSettingsLocked(caller.getUserId());
- }
- }
+ mSupervisionManagerInternal.setSupervisionLockscreenEnabledForUser(
+ caller.getUserId(), enabled, options);
} else {
Objects.requireNonNull(who, "ComponentName is null");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c5d42ad9f081..8e06ed8cc283 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2714,16 +2714,18 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(AuthService.class);
t.traceEnd();
- if (android.security.Flags.secureLockdown()) {
- t.traceBegin("StartSecureLockDeviceService.Lifecycle");
- mSystemServiceManager.startService(SecureLockDeviceService.Lifecycle.class);
- t.traceEnd();
- }
+ if (!isWatch && !isTv && !isAutomotive) {
+ if (android.security.Flags.secureLockdown()) {
+ t.traceBegin("StartSecureLockDeviceService.Lifecycle");
+ mSystemServiceManager.startService(SecureLockDeviceService.Lifecycle.class);
+ t.traceEnd();
+ }
- if (android.adaptiveauth.Flags.enableAdaptiveAuth()) {
- t.traceBegin("StartAuthenticationPolicyService");
- mSystemServiceManager.startService(AuthenticationPolicyService.class);
- t.traceEnd();
+ if (android.adaptiveauth.Flags.enableAdaptiveAuth()) {
+ t.traceBegin("StartAuthenticationPolicyService");
+ mSystemServiceManager.startService(AuthenticationPolicyService.class);
+ t.traceEnd();
+ }
}
if (!isWatch) {
diff --git a/services/print/java/com/android/server/print/flags.aconfig b/services/print/java/com/android/server/print/flags.aconfig
index 0210791cfeda..42d142545660 100644
--- a/services/print/java/com/android/server/print/flags.aconfig
+++ b/services/print/java/com/android/server/print/flags.aconfig
@@ -3,7 +3,7 @@ container: "system"
flag {
name: "do_not_include_capabilities"
- namespace: "print"
+ namespace: "printing"
description: "Do not use the flag Context.BIND_INCLUDE_CAPABILITIES when binding to the service"
bug: "291281543"
}
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index 4e9fff230bac..b80d68de0f2e 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -37,6 +37,7 @@ import static org.testng.Assert.expectThrows;
import android.annotation.UserIdInt;
import android.app.Application;
+import android.app.backup.BackupManagerInternal;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
@@ -52,6 +53,7 @@ import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
+import com.android.server.LocalServices;
import com.android.server.SystemService.TargetUser;
import com.android.server.backup.testing.TransportData;
import com.android.server.testing.shadows.ShadowApplicationPackageManager;
@@ -229,7 +231,7 @@ public class BackupManagerServiceRoboTest {
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
IBinder agentBinder = mock(IBinder.class);
- backupManagerService.agentConnected(mUserOneId, TEST_PACKAGE, agentBinder);
+ backupManagerService.agentConnectedForUser(TEST_PACKAGE, mUserOneId, agentBinder);
verify(mUserOneBackupAgentConnectionManager).agentConnected(TEST_PACKAGE, agentBinder);
}
@@ -242,7 +244,7 @@ public class BackupManagerServiceRoboTest {
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
IBinder agentBinder = mock(IBinder.class);
- backupManagerService.agentConnected(mUserTwoId, TEST_PACKAGE, agentBinder);
+ backupManagerService.agentConnectedForUser(TEST_PACKAGE, mUserTwoId, agentBinder);
verify(mUserOneBackupAgentConnectionManager, never()).agentConnected(TEST_PACKAGE,
agentBinder);
@@ -1549,6 +1551,7 @@ public class BackupManagerServiceRoboTest {
@Test
public void testOnStart_publishesService() {
BackupManagerService backupManagerService = mock(BackupManagerService.class);
+ LocalServices.removeServiceForTest(BackupManagerInternal.class);
BackupManagerService.Lifecycle lifecycle =
spy(new BackupManagerService.Lifecycle(mContext, backupManagerService));
doNothing().when(lifecycle).publishService(anyString(), any());
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index c4a042370de5..f1f4a0e83a79 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -39,6 +39,7 @@ import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.backup.BackupManager;
+import android.app.backup.BackupManagerInternal;
import android.app.backup.ISelectBackupTransportCallback;
import android.app.job.JobScheduler;
import android.content.ComponentName;
@@ -59,6 +60,7 @@ import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.backup.utils.RandomAccessFileUtils;
@@ -716,7 +718,7 @@ public class BackupManagerServiceTest {
// Create BMS *before* setting a main user to simulate the main user being created after
// BMS, which can happen for the first ever boot of a new device.
mService = new BackupManagerServiceTestable(mContextMock);
- mServiceLifecycle = new BackupManagerService.Lifecycle(mContextMock, mService);
+ createBackupServiceLifecycle(mContextMock, mService);
when(mUserManagerMock.getMainUser()).thenReturn(UserHandle.of(NON_SYSTEM_USER));
assertFalse(mService.isBackupServiceActive(NON_SYSTEM_USER));
@@ -730,7 +732,7 @@ public class BackupManagerServiceTest {
// Create BMS *before* setting a main user to simulate the main user being created after
// BMS, which can happen for the first ever boot of a new device.
mService = new BackupManagerServiceTestable(mContextMock);
- mServiceLifecycle = new BackupManagerService.Lifecycle(mContextMock, mService);
+ createBackupServiceLifecycle(mContextMock, mService);
when(mUserManagerMock.getMainUser()).thenReturn(UserHandle.of(NON_SYSTEM_USER));
assertFalse(mService.isBackupServiceActive(NON_SYSTEM_USER));
@@ -754,7 +756,7 @@ public class BackupManagerServiceTest {
private void createBackupManagerServiceAndUnlockSystemUser() {
mService = new BackupManagerServiceTestable(mContextMock);
- mServiceLifecycle = new BackupManagerService.Lifecycle(mContextMock, mService);
+ createBackupServiceLifecycle(mContextMock, mService);
simulateUserUnlocked(UserHandle.USER_SYSTEM);
}
@@ -765,7 +767,15 @@ public class BackupManagerServiceTest {
private void setMockMainUserAndCreateBackupManagerService(int userId) {
when(mUserManagerMock.getMainUser()).thenReturn(UserHandle.of(userId));
mService = new BackupManagerServiceTestable(mContextMock);
- mServiceLifecycle = new BackupManagerService.Lifecycle(mContextMock, mService);
+ createBackupServiceLifecycle(mContextMock, mService);
+ }
+
+ private void createBackupServiceLifecycle(Context context, BackupManagerService service) {
+ // Anytime we manually create the Lifecycle, we need to remove the internal BMS because
+ // it would've been added already at boot time and LocalServices does not allow
+ // overriding an existing service.
+ LocalServices.removeServiceForTest(BackupManagerInternal.class);
+ mServiceLifecycle = new BackupManagerService.Lifecycle(context, service);
}
private void simulateUserUnlocked(int userId) {
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index cd94c0f6e245..e61571288ade 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -70,8 +70,6 @@ import android.os.PerformanceHintManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SessionCreationConfig;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -1388,7 +1386,6 @@ public class HintManagerServiceTest {
@Test
- @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK})
public void testCpuHeadroomCache() throws Exception {
CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
CpuHeadroomParams halParams1 = new CpuHeadroomParams();
@@ -1476,8 +1473,7 @@ public class HintManagerServiceTest {
}
@Test
- @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK})
- public void testGetCpuHeadroomDifferentAffinity_flagOn() throws Exception {
+ public void testGetCpuHeadroomDifferentAffinity() throws Exception {
CountDownLatch latch = new CountDownLatch(2);
int[] tids = createThreads(2, latch);
CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
@@ -1497,28 +1493,6 @@ public class HintManagerServiceTest {
verify(mIPowerMock, times(0)).getCpuHeadroom(any());
}
- @Test
- @DisableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK})
- public void testGetCpuHeadroomDifferentAffinity_flagOff() throws Exception {
- CountDownLatch latch = new CountDownLatch(2);
- int[] tids = createThreads(2, latch);
- CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
- params.tids = tids;
- CpuHeadroomParams halParams = new CpuHeadroomParams();
- halParams.tids = tids;
- float headroom = 0.1f;
- CpuHeadroomResult halRet = CpuHeadroomResult.globalHeadroom(headroom);
- String ret1 = runAndWaitForCommand("taskset -p 1 " + tids[0]);
- String ret2 = runAndWaitForCommand("taskset -p 3 " + tids[1]);
-
- HintManagerService service = createService();
- clearInvocations(mIPowerMock);
- when(mIPowerMock.getCpuHeadroom(eq(halParams))).thenReturn(halRet);
- assertEquals("taskset cmd return: " + ret1 + "\n" + ret2, halRet,
- service.getBinderServiceInstance().getCpuHeadroom(params));
- verify(mIPowerMock, times(1)).getCpuHeadroom(any());
- }
-
private String runAndWaitForCommand(String command) throws Exception {
java.lang.Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index d6ca10a23fb9..07b18db59960 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -27,6 +27,7 @@ android_test {
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
+ "apct-perftests-utils",
],
libs: [
@@ -64,10 +65,12 @@ android_ravenwood_test {
"ravenwood-junit",
"truth",
"androidx.annotation_annotation",
+ "androidx.test.ext.junit",
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"modules-utils-binary-xml",
"flag-junit",
+ "apct-perftests-utils",
],
srcs: [
"src/com/android/server/power/stats/*.java",
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTraceTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTraceTest.java
new file mode 100644
index 000000000000..cc75e9e3114f
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTraceTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.ParcelFileDescriptor;
+import android.perftests.utils.TraceMarkParser;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Atrace event test")
+public class BatteryStatsHistoryTraceTest {
+ private static final String ATRACE_START = "atrace --async_start -b 1024 -c ss";
+ private static final String ATRACE_STOP = "atrace --async_stop";
+ private static final String ATRACE_DUMP = "atrace --async_dump";
+
+ @Before
+ public void before() throws Exception {
+ runShellCommand(ATRACE_START);
+ }
+
+ @After
+ public void after() throws Exception {
+ runShellCommand(ATRACE_STOP);
+ }
+
+ @Test
+ public void dumpsys() throws Exception {
+ runShellCommand("dumpsys batterystats --history");
+
+ Set<String> slices = readAtraceSlices();
+ assertThat(slices).contains("BatteryStatsHistory.copy");
+ assertThat(slices).contains("BatteryStatsHistory.iterate");
+ }
+
+ @Test
+ public void getBatteryUsageStats() throws Exception {
+ BatteryStatsManager batteryStatsManager =
+ getInstrumentation().getTargetContext().getSystemService(BatteryStatsManager.class);
+ BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+ .includeBatteryHistory().build();
+ BatteryUsageStats batteryUsageStats = batteryStatsManager.getBatteryUsageStats(query);
+ assertThat(batteryUsageStats).isNotNull();
+
+ Set<String> slices = readAtraceSlices();
+ assertThat(slices).contains("BatteryStatsHistory.copy");
+ assertThat(slices).contains("BatteryStatsHistory.iterate");
+ assertThat(slices).contains("BatteryStatsHistory.writeToParcel");
+ }
+
+ private String runShellCommand(String cmd) throws Exception {
+ return UiDevice.getInstance(getInstrumentation()).executeShellCommand(cmd);
+ }
+
+ private Set<String> readAtraceSlices() throws Exception {
+ Set<String> keys = new HashSet<>();
+
+ TraceMarkParser parser = new TraceMarkParser(
+ line -> line.name.startsWith("BatteryStatsHistory."));
+ ParcelFileDescriptor pfd =
+ getInstrumentation().getUiAutomation().executeShellCommand(ATRACE_DUMP);
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfd)))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ parser.visit(line);
+ }
+ }
+ parser.forAllSlices((key, slices) -> keys.add(key));
+ return keys;
+ }
+}
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/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index 0bf419ec242c..998c1d1a23b1 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -35,6 +36,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.DropBoxManager;
@@ -48,6 +50,7 @@ import android.os.Parcel;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -68,6 +71,7 @@ import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -150,6 +154,25 @@ public class ActivityManagerTest {
}
@Test
+ public void testRemovedUserShouldNotBeRunning() throws Exception {
+ final UserManager userManager = mContext.getSystemService(UserManager.class);
+ assertNotNull("UserManager should not be null", userManager);
+ final UserInfo user = userManager.createUser(
+ "TestUser", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+ mService.startUserInBackground(user.id);
+ assertTrue("User should be running", mService.isUserRunning(user.id, 0));
+ assertTrue("User should be in running users",
+ Arrays.stream(mService.getRunningUserIds()).anyMatch(x -> x == user.id));
+
+ userManager.removeUser(user.id);
+ mService.startUserInBackground(user.id);
+ assertFalse("Removed user should not be running", mService.isUserRunning(user.id, 0));
+ assertFalse("Removed user should not be in running users",
+ Arrays.stream(mService.getRunningUserIds()).anyMatch(x -> x == user.id));
+ }
+
+ @Test
public void testServiceUnbindAndKilling() {
for (int i = TEST_LOOPS; i > 0; i--) {
runOnce(i);
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 6411463fe0d9..06958b81d846 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -490,29 +490,6 @@ public class UserControllerTest {
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- verify(mInjector, times(0)).dismissKeyguard(any());
- verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
- verifySystemUserVisibilityChangesNeverNotified();
- }
-
- @Test
- public void testContinueUserSwitchDismissKeyguard() {
- when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(false);
- mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
- /* backgroundUserScheduledStopTimeSecs= */ -1);
- // Start user -- this will update state of mUserController
- mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
- Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
- assertNotNull(reportMsg);
- UserState userState = (UserState) reportMsg.obj;
- int oldUserId = reportMsg.arg1;
- int newUserId = reportMsg.arg2;
- mInjector.mHandler.clearAllRecordedMessages();
- // Verify that continueUserSwitch worked as expected
- continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- verify(mInjector, times(1)).dismissKeyguard(any());
verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
verifySystemUserVisibilityChangesNeverNotified();
@@ -1923,11 +1900,6 @@ public class UserControllerTest {
}
@Override
- protected void dismissKeyguard(Runnable runnable) {
- runnable.run();
- }
-
- @Override
void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
Runnable onShown) {
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..776f05dfb6ea 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -402,7 +402,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void testPushDynamicShortcut() {
+ public void disabled_testPushDynamicShortcut() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
+ ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
@@ -544,7 +544,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_10));
}
- public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
+ public void disabled_testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
throws InterruptedException {
mService.updateConfigurationLocked(
ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500");
@@ -627,7 +627,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals(3, mManager.getRemainingCallCount());
}
- public void testPublishWithNoActivity() {
+ public void disbabledTestPublishWithNoActivity() {
// If activity is not explicitly set, use the default one.
mRunningUsers.put(USER_11, true);
@@ -733,7 +733,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void testPublishWithNoActivity_noMainActivityInPackage() {
+ public void disabled_testPublishWithNoActivity_noMainActivityInPackage() {
mRunningUsers.put(USER_11, true);
runWithCaller(CALLING_PACKAGE_2, USER_11, () -> {
@@ -2557,7 +2557,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void testPinShortcutAndGetPinnedShortcuts_multi() {
+ public void disabled_testPinShortcutAndGetPinnedShortcuts_multi() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
@@ -2889,7 +2889,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
+ public void disabled_testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
@@ -3919,7 +3919,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Try save and load, also stop/start the user.
*/
- public void testSaveAndLoadUser() {
+ public void disabled_testSaveAndLoadUser() {
// First, create some shortcuts and save.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
@@ -8579,7 +8579,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void testReturnedByServer() {
+ public void disabled_testReturnedByServer() {
// Package 1 updated, with manifest shortcuts.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 6cb24293a7d5..fa733e85c89c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2509,6 +2509,134 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testRepostWithNewChannel_afterAutogrouping_isRegrouped() {
+ final String pkg = "package";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ // Post ungrouped notifications => will be autogrouped
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = getNotificationRecord(pkg, i + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, null, false);
+ notificationList.add(notification);
+ mGroupHelper.onNotificationPosted(notification, false);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Post ungrouped notifications to a different section, below autogroup limit
+ Mockito.reset(mCallback);
+ // Post ungrouped notifications => will be autogrouped
+ final NotificationChannel silentChannel = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_LOW);
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord notification = getNotificationRecord(pkg, i + 4242,
+ String.valueOf(i + 4242), UserHandle.SYSTEM, null, false, silentChannel);
+ notificationList.add(notification);
+ mGroupHelper.onNotificationPosted(notification, false);
+ }
+
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+
+ // Update a notification to a different channel that moves it to a different section
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ final NotificationSectioner initialSection = GroupHelper.getSection(notifToInvalidate);
+ final NotificationChannel updatedChannel = new NotificationChannel("TEST_CHANNEL_ID2",
+ "TEST_CHANNEL_ID2", IMPORTANCE_LOW);
+ notifToInvalidate.updateNotificationChannel(updatedChannel);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNotEqualTo(initialSection);
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, false);
+ assertThat(needsAutogrouping).isTrue();
+
+ // Check that the silent section was autogrouped
+ final String silentSectionGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(silentSectionGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(silentSectionGroupKey), eq(true));
+ verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testRepostWithNewChannel_afterForceGrouping_isRegrouped() {
+ final String pkg = "package";
+ final String groupName = "testGroup";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post valid section summary notifications without children => force group
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = getNotificationRecord(pkg, i + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, groupName, false);
+ notificationList.add(notification);
+ mGroupHelper.onNotificationPostedWithDelay(notification, notificationList,
+ summaryByGroup);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Update a notification to a different channel that moves it to a different section
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ final NotificationSectioner initialSection = GroupHelper.getSection(notifToInvalidate);
+ final NotificationChannel updatedChannel = new NotificationChannel("TEST_CHANNEL_ID2",
+ "TEST_CHANNEL_ID2", IMPORTANCE_LOW);
+ notifToInvalidate.updateNotificationChannel(updatedChannel);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNotEqualTo(initialSection);
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, false);
+
+ mGroupHelper.onNotificationPostedWithDelay(notifToInvalidate, notificationList,
+ summaryByGroup);
+
+ // Check that the updated notification is removed from the autogroup
+ assertThat(needsAutogrouping).isFalse();
+ verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+
+ // Post child notifications for the silent sectin => will be autogrouped
+ Mockito.reset(mCallback);
+ final NotificationChannel silentChannel = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_LOW);
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord notification = getNotificationRecord(pkg, i + 4242,
+ String.valueOf(i + 4242), UserHandle.SYSTEM, "aGroup", false, silentChannel);
+ notificationList.add(notification);
+ needsAutogrouping = mGroupHelper.onNotificationPosted(notification, false);
+ assertThat(needsAutogrouping).isFalse();
+ mGroupHelper.onNotificationPostedWithDelay(notification, notificationList,
+ summaryByGroup);
+ }
+
+ // Check that the silent section was autogrouped
+ final String silentSectionGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(silentSectionGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(silentSectionGroupKey), eq(true));
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel() {
final String pkg = "package";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 7885c9b902e2..e43b28bb9404 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6341,6 +6341,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testOnlyAutogroupIfNeeded_channelChanged_ghUpdate() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+ "testOnlyAutogroupIfNeeded_channelChanged_ghUpdate", null, false);
+ mService.addNotification(r);
+
+ NotificationRecord update = generateNotificationRecord(mSilentChannel, 0,
+ "testOnlyAutogroupIfNeeded_channelChanged_ghUpdate", null, false);
+ mService.addEnqueuedNotification(update);
+
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(update.getKey(),
+ update.getSbn().getPackageName(), update.getUid(),
+ mPostNotificationTrackerFactory.newTracker(null));
+ runnable.run();
+ waitForIdle();
+
+ verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+ }
+
+ @Test
public void testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate() {
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
"testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
@@ -17901,4 +17921,63 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary));
}
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testRebundleNotification_restoresBundleChannel() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+
+ // Post a single notification
+ final boolean hasOriginalSummary = false;
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ final String keyToUnbundle = r.getKey();
+ mService.addNotification(r);
+
+ // Classify notification into the NEWS bundle
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+ Adjustment adjustment = new Adjustment(
+ r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ // Check that the NotificationRecord channel is updated
+ assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+ assertThat(r.getBundleType()).isEqualTo(Adjustment.TYPE_NEWS);
+
+ // Unbundle the notification
+ mService.mNotificationDelegate.unbundleNotification(keyToUnbundle);
+
+ // Check that the original channel was restored
+ assertThat(r.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID);
+ assertThat(r.getBundleType()).isEqualTo(Adjustment.TYPE_NEWS);
+ verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r), eq(hasOriginalSummary));
+
+ Mockito.reset(mRankingHandler);
+ Mockito.reset(mGroupHelper);
+
+ // Rebundle the notification
+ mService.mNotificationDelegate.rebundleNotification(keyToUnbundle);
+
+ // Actually apply the adjustments
+ doAnswer(invocationOnMock -> {
+ ((NotificationRecord) invocationOnMock.getArguments()[0]).applyAdjustments();
+ ((NotificationRecord) invocationOnMock.getArguments()[0]).calculateImportance();
+ return null;
+ }).when(mRankingHelper).extractSignals(any(NotificationRecord.class));
+ mService.handleRankingSort();
+ verify(handler, times(1)).scheduleSendRankingUpdate();
+
+ // Check that the bundle channel was restored
+ verify(mRankingHandler, times(1)).requestSort();
+ assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+ }
+
}
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 8cc233b7594b..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;
@@ -3192,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");
@@ -3212,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");
@@ -3231,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");
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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 5486aa34b5fa..dfd10ec86a20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1183,6 +1183,18 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(prev, mDisplayContent.getLastOrientationSource());
// The top will use the rotation from "prev" with fixed rotation.
assertTrue(top.hasFixedRotationTransform());
+
+ mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
+ assertFalse(top.hasFixedRotationTransform());
+
+ // Assume that the requested orientation of "prev" is landscape. And the display is also
+ // rotated to landscape. The activities from bottom to top are TaskB{"prev, "behindTop"},
+ // TaskB{"top"}. Then "behindTop" should also get landscape according to ORIENTATION_BEHIND
+ // instead of resolving as undefined which causes to unexpected fixed portrait rotation.
+ final ActivityRecord behindTop = new ActivityBuilder(mAtm).setTask(prev.getTask())
+ .setOnTop(false).setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
+ mDisplayContent.applyFixedRotationForNonTopVisibleActivityIfNeeded(behindTop);
+ assertFalse(behindTop.hasFixedRotationTransform());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index de4b6fac7abf..23dcb65eb30f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -34,6 +34,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -46,12 +47,14 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ShortcutServiceInternal;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -60,6 +63,7 @@ import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.DragEvent;
import android.view.InputChannel;
@@ -74,6 +78,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
+import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.AfterClass;
@@ -141,17 +146,28 @@ public class DragDropControllerTests extends WindowTestsBase {
}
}
+ private WindowState createDropTargetWindow(String name) {
+ return createDropTargetWindow(name, null /* targetDisplay */);
+ }
+
/**
* Creates a window state which can be used as a drop target.
*/
- private WindowState createDropTargetWindow(String name, int ownerId) {
- final Task task = new TaskBuilder(mSupervisor).setUserId(ownerId).build();
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).setUseProcess(
- mProcess).build();
+ private WindowState createDropTargetWindow(String name,
+ @Nullable DisplayContent targetDisplay) {
+ final WindowState window;
+ if (targetDisplay == null) {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).setUseProcess(
+ mProcess).build();
+ window = newWindowBuilder(name, TYPE_BASE_APPLICATION).setWindowToken(
+ activity).setClientWindow(new TestIWindow()).build();
+ } else {
+ window = newWindowBuilder(name, TYPE_BASE_APPLICATION).setDisplay(
+ targetDisplay).setClientWindow(new TestIWindow()).build();
+ }
// Use a new TestIWindow so we don't collect events for other windows
- final WindowState window = newWindowBuilder(name, TYPE_BASE_APPLICATION).setWindowToken(
- activity).setOwnerId(ownerId).setClientWindow(new TestIWindow()).build();
InputChannel channel = new InputChannel();
window.openInputChannel(channel);
window.mHasSurface = true;
@@ -174,7 +190,7 @@ public class DragDropControllerTests extends WindowTestsBase {
public void setUp() throws Exception {
mTarget = new TestDragDropController(mWm, mWm.mH.getLooper());
mProcess = mSystemServicesTestRule.addProcess(TEST_PACKAGE, "testProc", TEST_PID, TEST_UID);
- mWindow = createDropTargetWindow("Drag test window", 0);
+ mWindow = createDropTargetWindow("Drag test window");
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
when(mWm.mInputManager.startDragAndDrop(any(IBinder.class), any(IBinder.class))).thenReturn(
true);
@@ -263,8 +279,8 @@ public class DragDropControllerTests extends WindowTestsBase {
@Test
public void testPrivateInterceptGlobalDragDropIgnoresNonLocalWindows() {
- WindowState nonLocalWindow = createDropTargetWindow("App drag test window", 0);
- WindowState globalInterceptWindow = createDropTargetWindow("Global drag test window", 0);
+ WindowState nonLocalWindow = createDropTargetWindow("App drag test window");
+ WindowState globalInterceptWindow = createDropTargetWindow("Global drag test window");
globalInterceptWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
// Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
@@ -347,6 +363,120 @@ public class DragDropControllerTests extends WindowTestsBase {
});
}
+ @Test
+ public void testDragEventCoordinates() {
+ int dragStartX = mWindow.getBounds().centerX();
+ int dragStartY = mWindow.getBounds().centerY();
+ int startOffsetPx = 10;
+ int dropCoordsPx = 15;
+ WindowState window2 = createDropTargetWindow("App drag test window");
+ Rect bounds = new Rect(dragStartX + startOffsetPx, dragStartY + startOffsetPx,
+ mWindow.getBounds().right, mWindow.getBounds().bottom);
+ window2.setBounds(bounds);
+ window2.getFrame().set(bounds);
+
+ // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
+ // immediately after dispatching, which is a problem when using mockito arguments captor
+ // because it returns and modifies the same drag event.
+ TestIWindow iwindow = (TestIWindow) mWindow.mClient;
+ final ArrayList<DragEvent> dragEvents = new ArrayList<>();
+ iwindow.setDragEventJournal(dragEvents);
+ TestIWindow iwindow2 = (TestIWindow) window2.mClient;
+ final ArrayList<DragEvent> dragEvents2 = new ArrayList<>();
+ iwindow2.setDragEventJournal(dragEvents2);
+
+ startDrag(dragStartX, dragStartY, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ,
+ ClipData.newPlainText("label", "text"), () -> {
+ // Verify the start-drag event is sent as-is for the drag origin window.
+ final DragEvent dragEvent = dragEvents.get(0);
+ assertEquals(ACTION_DRAG_STARTED, dragEvent.getAction());
+ assertEquals(dragStartX, dragEvent.getX(), 0.0 /* delta */);
+ assertEquals(dragStartY, dragEvent.getY(), 0.0 /* delta */);
+ // Verify the start-drag event is sent relative to the window top-left.
+ final DragEvent dragEvent2 = dragEvents2.get(0);
+ assertEquals(ACTION_DRAG_STARTED, dragEvent2.getAction());
+ assertEquals(-startOffsetPx, dragEvent2.getX(), 0.0 /* delta */);
+ assertEquals(-startOffsetPx, dragEvent2.getY(), 0.0 /* delta */);
+
+ try {
+ mTarget.mDeferDragStateClosed = true;
+ // x, y is window-local coordinate.
+ mTarget.reportDropWindow(window2.mInputChannelToken, dropCoordsPx,
+ dropCoordsPx);
+ mTarget.handleMotionEvent(false, window2.getDisplayId(), dropCoordsPx,
+ dropCoordsPx);
+ mToken = window2.mClient.asBinder();
+ // Verify only window2 received the DROP event and coords are sent as-is.
+ assertEquals(1, dragEvents.size());
+ assertEquals(2, dragEvents2.size());
+ final DragEvent dropEvent = last(dragEvents2);
+ assertEquals(ACTION_DROP, dropEvent.getAction());
+ assertEquals(dropCoordsPx, dropEvent.getX(), 0.0 /* delta */);
+ assertEquals(dropCoordsPx, dropEvent.getY(), 0.0 /* delta */);
+
+ mTarget.reportDropResult(iwindow2, true);
+ } finally {
+ mTarget.mDeferDragStateClosed = false;
+ }
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND)
+ public void testDragEventConnectedDisplaysCoordinates() {
+ final DisplayContent testDisplay = createMockSimulatedDisplay();
+ int dragStartX = mWindow.getBounds().centerX();
+ int dragStartY = mWindow.getBounds().centerY();
+ int dropCoordsPx = 15;
+ WindowState window2 = createDropTargetWindow("App drag test window", testDisplay);
+
+ // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
+ // immediately after dispatching, which is a problem when using mockito arguments captor
+ // because it returns and modifies the same drag event.
+ TestIWindow iwindow = (TestIWindow) mWindow.mClient;
+ final ArrayList<DragEvent> dragEvents = new ArrayList<>();
+ iwindow.setDragEventJournal(dragEvents);
+ TestIWindow iwindow2 = (TestIWindow) window2.mClient;
+ final ArrayList<DragEvent> dragEvents2 = new ArrayList<>();
+ iwindow2.setDragEventJournal(dragEvents2);
+
+ startDrag(dragStartX, dragStartY, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ,
+ ClipData.newPlainText("label", "text"), () -> {
+ // Verify the start-drag event is sent as-is for the drag origin window.
+ final DragEvent dragEvent = dragEvents.get(0);
+ assertEquals(ACTION_DRAG_STARTED, dragEvent.getAction());
+ assertEquals(dragStartX, dragEvent.getX(), 0.0 /* delta */);
+ assertEquals(dragStartY, dragEvent.getY(), 0.0 /* delta */);
+ // Verify the start-drag event from different display is sent out of display
+ // bounds.
+ final DragEvent dragEvent2 = dragEvents2.get(0);
+ assertEquals(ACTION_DRAG_STARTED, dragEvent2.getAction());
+ assertEquals(-window2.getBounds().left - 1, dragEvent2.getX(), 0.0 /* delta */);
+ assertEquals(-window2.getBounds().top - 1, dragEvent2.getY(), 0.0 /* delta */);
+
+ try {
+ mTarget.mDeferDragStateClosed = true;
+ // x, y is window-local coordinate.
+ mTarget.reportDropWindow(window2.mInputChannelToken, dropCoordsPx,
+ dropCoordsPx);
+ mTarget.handleMotionEvent(false, window2.getDisplayId(), dropCoordsPx,
+ dropCoordsPx);
+ mToken = window2.mClient.asBinder();
+ // Verify only window2 received the DROP event and coords are sent as-is
+ assertEquals(1, dragEvents.size());
+ assertEquals(2, dragEvents2.size());
+ final DragEvent dropEvent = last(dragEvents2);
+ assertEquals(ACTION_DROP, dropEvent.getAction());
+ assertEquals(dropCoordsPx, dropEvent.getX(), 0.0 /* delta */);
+ assertEquals(dropCoordsPx, dropEvent.getY(), 0.0 /* delta */);
+
+ mTarget.reportDropResult(iwindow2, true);
+ } finally {
+ mTarget.mDeferDragStateClosed = false;
+ }
+ });
+ }
+
private DragEvent last(ArrayList<DragEvent> list) {
return list.get(list.size() - 1);
}
@@ -503,7 +633,7 @@ public class DragDropControllerTests extends WindowTestsBase {
@Test
public void testRequestSurfaceForReturnAnimationFlag_dropSuccessful() {
- WindowState otherWindow = createDropTargetWindow("App drag test window", 0);
+ WindowState otherWindow = createDropTargetWindow("App drag test window");
TestIWindow otherIWindow = (TestIWindow) otherWindow.mClient;
// Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
@@ -534,7 +664,7 @@ public class DragDropControllerTests extends WindowTestsBase {
@Test
public void testRequestSurfaceForReturnAnimationFlag_dropUnsuccessful() {
- WindowState otherWindow = createDropTargetWindow("App drag test window", 0);
+ WindowState otherWindow = createDropTargetWindow("App drag test window");
TestIWindow otherIWindow = (TestIWindow) otherWindow.mClient;
// Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
@@ -687,6 +817,14 @@ public class DragDropControllerTests extends WindowTestsBase {
* Starts a drag with the given parameters, calls Runnable `r` after drag is started.
*/
private void startDrag(int flag, ClipData data, Runnable r) {
+ startDrag(0, 0, flag, data, r);
+ }
+
+ /**
+ * Starts a drag with the given parameters, calls Runnable `r` after drag is started.
+ */
+ private void startDrag(float startInWindowX, float startInWindowY, int flag, ClipData data,
+ Runnable r) {
final SurfaceSession appSession = new SurfaceSession();
try {
final SurfaceControl surface = new SurfaceControl.Builder(appSession).setName(
@@ -694,8 +832,8 @@ public class DragDropControllerTests extends WindowTestsBase {
PixelFormat.TRANSLUCENT).build();
assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new Binder()));
- mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
- 0, 0, data);
+ mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient, flag, surface, 0, 0, 0,
+ startInWindowX, startInWindowY, 0, 0, data);
assertNotNull(mToken);
r.run();
diff --git a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
index 3e87f1f96fcd..ee9673f5ee77 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
@@ -177,15 +177,16 @@ public class PersisterQueueTests {
assertTrue("Target didn't call callback enough times.",
mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
+ // Wait until writing thread is waiting, which indicates the thread is waiting for new tasks
+ // to appear.
+ assertTrue("Failed to wait until the writing thread is waiting.",
+ mTarget.waitUntilWritingThreadIsWaiting(TIMEOUT_ALLOWANCE));
+
// Second item
mFactory.setExpectedProcessedItemNumber(1);
mListener.setExpectedOnPreProcessItemCallbackTimes(1);
dispatchTime = SystemClock.uptimeMillis();
- // Synchronize on the instance to make sure we schedule the item after it starts to wait for
- // task indefinitely.
- synchronized (mTarget) {
- mTarget.addItem(mFactory.createItem(), false);
- }
+ mTarget.addItem(mFactory.createItem(), false);
assertTrue("Target didn't process item enough times.",
mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
assertEquals("Target didn't process all items.", 2, mFactory.getTotalProcessedItemCount());
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 d6080e08774e..07ee09a350d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -185,31 +185,46 @@ public class SizeCompatTests extends WindowTestsBase {
}
private ActivityRecord setUpApp(DisplayContent display) {
- return setUpApp(display, null /* appBuilder */);
+ return setUpApp(display, null /* appBuilder */, null /* taskBuilder */);
}
private ActivityRecord setUpApp(DisplayContent display, ActivityBuilder appBuilder) {
+ return setUpApp(display, appBuilder, null /* taskBuilder */);
+ }
+
+ private ActivityRecord setUpApp(DisplayContent display, ActivityBuilder aBuilder,
+ TaskBuilder tBuilder) {
// Use the real package name (com.android.frameworks.wmtests) so that
// EnableCompatChanges/DisableCompatChanges can take effect.
// Otherwise the fake WindowTestsBase.DEFAULT_COMPONENT_PACKAGE_NAME will make
// PlatformCompat#isChangeEnabledByPackageName always return default value.
final ComponentName componentName = ComponentName.createRelative(
mContext, SizeCompatTests.class.getName());
- mTask = new TaskBuilder(mSupervisor).setDisplay(display).setComponent(componentName)
+ final TaskBuilder taskBuilder = tBuilder != null ? tBuilder : new TaskBuilder(mSupervisor);
+ mTask = taskBuilder.setDisplay(display).setComponent(componentName)
.build();
- final ActivityBuilder builder = appBuilder != null ? appBuilder : new ActivityBuilder(mAtm);
- mActivity = builder.setTask(mTask).setComponent(componentName).build();
+ final ActivityBuilder appBuilder = aBuilder != null ? aBuilder : new ActivityBuilder(mAtm);
+ mActivity = appBuilder.setTask(mTask).setComponent(componentName).build();
doReturn(false).when(mActivity).isImmersiveMode(any());
return mActivity;
}
private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh) {
- return setUpDisplaySizeWithApp(dw, dh, null /* appBuilder */);
+ return setUpDisplaySizeWithApp(dw, dh, null /* appBuilder */, null /* taskBuilder */);
}
private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh, ActivityBuilder appBuilder) {
+ return setUpDisplaySizeWithApp(dw, dh, appBuilder, null /* taskBuilder */);
+ }
+
+ private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh, TaskBuilder taskBuilder) {
+ return setUpDisplaySizeWithApp(dw, dh, null /* appBuilder */, taskBuilder);
+ }
+
+ private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh, ActivityBuilder appBuilder,
+ TaskBuilder taskBuilder) {
final TestDisplayContent.Builder builder = new TestDisplayContent.Builder(mAtm, dw, dh);
- return setUpApp(builder.build(), appBuilder);
+ return setUpApp(builder.build(), appBuilder, taskBuilder);
}
private void setUpLargeScreenDisplayWithApp(int dw, int dh) {
@@ -834,7 +849,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();
@@ -4469,6 +4484,80 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(new Rect(0, 0, 1000, 2800), bounds);
}
+ @Test
+ @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
+ public void testUserAspectRatioOverridesNotAppliedToResizeableFreeformActivity() {
+ final TaskBuilder taskBuilder =
+ new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
+ setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
+
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mWmService.mAppCompatConfiguration);
+ doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
+ .isUserAppAspectRatioSettingsEnabled();
+ final AppCompatController appCompatController = mActivity.mAppCompatController;
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ appCompatController.getAppCompatAspectRatioOverrides();
+ spyOn(aspectRatioOverrides);
+ // Set user aspect ratio override.
+ doReturn(USER_MIN_ASPECT_RATIO_16_9).when(aspectRatioOverrides)
+ .getUserMinAspectRatioOverrideCode();
+
+ prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
+ assertFalse(appCompatController.getAppCompatAspectRatioPolicy().isAspectRatioApplied());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
+ public void testUserAspectRatioOverridesAppliedToNonResizeableFreeformActivity() {
+ final TaskBuilder taskBuilder =
+ new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
+ setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
+
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mWmService.mAppCompatConfiguration);
+ doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
+ .isUserAppAspectRatioSettingsEnabled();
+ final AppCompatController appCompatController = mActivity.mAppCompatController;
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ appCompatController.getAppCompatAspectRatioOverrides();
+ spyOn(aspectRatioOverrides);
+ // Set user aspect ratio override.
+ doReturn(USER_MIN_ASPECT_RATIO_16_9).when(aspectRatioOverrides)
+ .getUserMinAspectRatioOverrideCode();
+
+ prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ true);
+ assertTrue(appCompatController.getAppCompatAspectRatioPolicy().isAspectRatioApplied());
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+ @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
+ public void testSystemAspectRatioOverridesNotAppliedToResizeableFreeformActivity() {
+ final TaskBuilder taskBuilder =
+ new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
+ setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
+ prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
+
+ assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isAspectRatioApplied());
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+ @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
+ public void testSystemAspectRatioOverridesAppliedToNonResizeableFreeformActivity() {
+ final TaskBuilder taskBuilder =
+ new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
+ setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
+ prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ true);
+
+ assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isAspectRatioApplied());
+ }
+
private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox,
Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index d1f5d157560b..be79160c3a09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -76,6 +76,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.IApplicationThread;
import android.content.pm.ActivityInfo;
@@ -1522,7 +1523,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
@EnableFlags(Flags.FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE)
public void setConfigurationChangeSettingsForUser_createsFromParcel_callsSettingImpl()
throws Settings.SettingNotFoundException {
- final int userId = 0;
+ final int currentUserId = ActivityManager.getCurrentUser();
final int forcedDensity = 400;
final float forcedFontScaleFactor = 1.15f;
final Parcelable.Creator<ConfigurationChangeSetting> creator =
@@ -1536,10 +1537,10 @@ public class WindowManagerServiceTests extends WindowTestsBase {
mWm.setConfigurationChangeSettingsForUser(settings, UserHandle.USER_CURRENT);
- verify(mDisplayContent).setForcedDensity(forcedDensity, userId);
+ verify(mDisplayContent).setForcedDensity(forcedDensity, currentUserId);
assertEquals(forcedFontScaleFactor, Settings.System.getFloat(
mContext.getContentResolver(), Settings.System.FONT_SCALE), 0.1f /* delta */);
- verify(mAtm).updateFontScaleIfNeeded(userId);
+ verify(mAtm).updateFontScaleIfNeeded(currentUserId);
}
@Test
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/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/AndroidManifest.xml b/tests/Input/AndroidManifest.xml
index 914adc40194d..8d380f0d72a6 100644
--- a/tests/Input/AndroidManifest.xml
+++ b/tests/Input/AndroidManifest.xml
@@ -32,7 +32,7 @@
android:process=":externalProcess">
</activity>
- <activity android:name="com.android.test.input.CaptureEventActivity"
+ <activity android:name="com.android.cts.input.CaptureEventActivity"
android:label="Capture events"
android:configChanges="touchscreen|uiMode|orientation|screenSize|screenLayout|keyboardHidden|uiMode|navigation|keyboard|density|fontScale|layoutDirection|locale|mcc|mnc|smallestScreenSize"
android:enableOnBackInvokedCallback="false"
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index d35c9008e8cb..8c04f647fb2f 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -59,6 +59,7 @@ import junitparams.Parameters
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -1466,20 +1467,32 @@ class KeyGestureControllerTests {
@Parameters(method = "customInputGesturesTestArguments")
fun testCustomKeyGestures(test: TestData) {
setupKeyGestureController()
+ val trigger = InputGestureData.createKeyTrigger(
+ test.expectedKeys[0],
+ test.expectedModifierState
+ )
val builder = InputGestureData.Builder()
.setKeyGestureType(test.expectedKeyGestureType)
- .setTrigger(
- InputGestureData.createKeyTrigger(
- test.expectedKeys[0],
- test.expectedModifierState
- )
- )
+ .setTrigger(trigger)
if (test.expectedAppLaunchData != null) {
builder.setAppLaunchData(test.expectedAppLaunchData)
}
val inputGestureData = builder.build()
- keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
+ assertNull(
+ test.toString(),
+ keyGestureController.getInputGesture(0, trigger.aidlTrigger)
+ )
+ assertEquals(
+ test.toString(),
+ InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS,
+ keyGestureController.addCustomInputGesture(0, builder.build().aidlData)
+ )
+ assertEquals(
+ test.toString(),
+ inputGestureData.aidlData,
+ keyGestureController.getInputGesture(0, trigger.aidlTrigger)
+ )
testKeyGestureInternal(test)
}
diff --git a/tests/Input/src/com/android/test/input/CaptureEventActivity.kt b/tests/Input/src/com/android/test/input/CaptureEventActivity.kt
deleted file mode 100644
index d54e3470d9c4..000000000000
--- a/tests/Input/src/com/android/test/input/CaptureEventActivity.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.test.input
-
-import android.app.Activity
-import android.os.Bundle
-import android.view.InputEvent
-import android.view.KeyEvent
-import android.view.MotionEvent
-import java.util.concurrent.LinkedBlockingQueue
-import java.util.concurrent.TimeUnit
-import org.junit.Assert.assertNull
-
-class CaptureEventActivity : Activity() {
- private val events = LinkedBlockingQueue<InputEvent>()
- var shouldHandleKeyEvents = true
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- // Set the fixed orientation if requested
- if (intent.hasExtra(EXTRA_FIXED_ORIENTATION)) {
- val orientation = intent.getIntExtra(EXTRA_FIXED_ORIENTATION, 0)
- setRequestedOrientation(orientation)
- }
-
- // Set the flag if requested
- if (intent.hasExtra(EXTRA_WINDOW_FLAGS)) {
- val flags = intent.getIntExtra(EXTRA_WINDOW_FLAGS, 0)
- window.addFlags(flags)
- }
- }
-
- override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
- events.add(MotionEvent.obtain(ev))
- return true
- }
-
- override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
- events.add(MotionEvent.obtain(ev))
- return true
- }
-
- override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
- events.add(KeyEvent(event))
- return shouldHandleKeyEvents
- }
-
- override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
- events.add(MotionEvent.obtain(ev))
- return true
- }
-
- fun getInputEvent(): InputEvent? {
- return events.poll(5, TimeUnit.SECONDS)
- }
-
- fun hasReceivedEvents(): Boolean {
- return !events.isEmpty()
- }
-
- fun assertNoEvents() {
- val event = events.poll(100, TimeUnit.MILLISECONDS)
- assertNull("Expected no events, but received $event", event)
- }
-
- companion object {
- const val EXTRA_FIXED_ORIENTATION = "fixed_orientation"
- const val EXTRA_WINDOW_FLAGS = "window_flags"
- }
-}
diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
index 0b281d8d39e2..9e0f7347943d 100644
--- a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
+++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
@@ -28,6 +28,7 @@ import android.view.InputEvent
import android.view.MotionEvent
import androidx.test.platform.app.InstrumentationRegistry
import com.android.cts.input.BatchedEventSplitter
+import com.android.cts.input.CaptureEventActivity
import com.android.cts.input.InputJsonParser
import com.android.cts.input.VirtualDisplayActivityScenario
import com.android.cts.input.inputeventmatchers.isResampled
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java
index 060133df0a40..e7e3d10c958b 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java
@@ -81,7 +81,8 @@ public final class PlatformCompatPermissionsTest {
thrown.expect(SecurityException.class);
final String packageName = mContext.getPackageName();
- mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.reportChange(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -90,7 +91,8 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE);
final String packageName = mContext.getPackageName();
- mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.reportChange(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -99,7 +101,7 @@ public final class PlatformCompatPermissionsTest {
thrown.expect(SecurityException.class);
final String packageName = mContext.getPackageName();
- mPlatformCompat.reportChangeByPackageName(1, packageName, 0);
+ mPlatformCompat.reportChangeByPackageName(1, packageName, Process.myUid());
}
@Test
@@ -108,7 +110,7 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE);
final String packageName = mContext.getPackageName();
- mPlatformCompat.reportChangeByPackageName(1, packageName, 0);
+ mPlatformCompat.reportChangeByPackageName(1, packageName, Process.myUid());
}
@Test
@@ -133,7 +135,8 @@ public final class PlatformCompatPermissionsTest {
thrown.expect(SecurityException.class);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.isChangeEnabled(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -143,7 +146,8 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.isChangeEnabled(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -152,7 +156,8 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.isChangeEnabled(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -161,7 +166,7 @@ public final class PlatformCompatPermissionsTest {
thrown.expect(SecurityException.class);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+ mPlatformCompat.isChangeEnabledByPackageName(1, packageName, Process.myUid());
}
@Test
@@ -171,7 +176,7 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+ mPlatformCompat.isChangeEnabledByPackageName(1, packageName, Process.myUid());
}
@Test
@@ -180,7 +185,7 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+ mPlatformCompat.isChangeEnabledByPackageName(1, packageName, Process.myUid());
}
@Test
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index 20315561cceb..f00a6cad6b46 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -54,7 +54,9 @@ std::string GetSafePath(StringPiece arg) {
void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value,
uint32_t flags) {
auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
- *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
+ if (value) {
+ *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
+ }
return true;
};
@@ -65,7 +67,9 @@ void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::st
void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
std::vector<std::string>* value, uint32_t flags) {
auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
- value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
+ if (value) {
+ value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
+ }
return true;
};
@@ -76,7 +80,9 @@ void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
void Command::AddOptionalFlag(StringPiece name, StringPiece description,
std::optional<std::string>* value, uint32_t flags) {
auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
- *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
+ if (value) {
+ *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
+ }
return true;
};
@@ -87,7 +93,9 @@ void Command::AddOptionalFlag(StringPiece name, StringPiece description,
void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
std::vector<std::string>* value, uint32_t flags) {
auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
- value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
+ if (value) {
+ value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
+ }
return true;
};
@@ -98,7 +106,9 @@ void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
std::unordered_set<std::string>* value) {
auto func = [value](StringPiece arg, std::ostream* out_error) -> bool {
- value->emplace(arg);
+ if (value) {
+ value->emplace(arg);
+ }
return true;
};
@@ -108,7 +118,9 @@ void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) {
auto func = [value](StringPiece arg, std::ostream* out_error) -> bool {
- *value = true;
+ if (value) {
+ *value = true;
+ }
return true;
};
diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp
index 2a3cb2a0c65d..ad167c979662 100644
--- a/tools/aapt2/cmd/Command_test.cpp
+++ b/tools/aapt2/cmd/Command_test.cpp
@@ -159,4 +159,22 @@ TEST(CommandTest, ShortOptions) {
ASSERT_NE(0, command.Execute({"-w"s, "2"s}, &std::cerr));
}
+TEST(CommandTest, OptionsWithNullptrToAcceptValues) {
+ TestCommand command;
+ command.AddRequiredFlag("--rflag", "", nullptr);
+ command.AddRequiredFlagList("--rlflag", "", nullptr);
+ command.AddOptionalFlag("--oflag", "", nullptr);
+ command.AddOptionalFlagList("--olflag", "", (std::vector<std::string>*)nullptr);
+ command.AddOptionalFlagList("--olflag2", "", (std::unordered_set<std::string>*)nullptr);
+ command.AddOptionalSwitch("--switch", "", nullptr);
+
+ ASSERT_EQ(0, command.Execute({
+ "--rflag"s, "1"s,
+ "--rlflag"s, "1"s,
+ "--oflag"s, "1"s,
+ "--olflag"s, "1"s,
+ "--olflag2"s, "1"s,
+ "--switch"s}, &std::cerr));
+}
+
} // namespace aapt \ No newline at end of file
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 6c3eae11eab9..060bc5fa2242 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -425,9 +425,6 @@ int ConvertCommand::Action(const std::vector<std::string>& args) {
<< output_format_.value());
return 1;
}
- if (enable_sparse_encoding_) {
- table_flattener_options_.sparse_entries = SparseEntriesMode::Enabled;
- }
if (force_sparse_encoding_) {
table_flattener_options_.sparse_entries = SparseEntriesMode::Forced;
}
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index 9452e588953e..98c8f5ff89c0 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -36,11 +36,9 @@ class ConvertCommand : public Command {
kOutputFormatProto, kOutputFormatBinary, kOutputFormatBinary), &output_format_);
AddOptionalSwitch(
"--enable-sparse-encoding",
- "Enables encoding sparse entries using a binary search tree.\n"
- "This decreases APK size at the cost of resource retrieval performance.\n"
- "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
- "the APK is O+",
- &enable_sparse_encoding_);
+ "[DEPRECATED] This flag is a no-op as of aapt2 v2.20. Sparse encoding is always\n"
+ "enabled if minSdk of the APK is >= 32.",
+ nullptr);
AddOptionalSwitch("--force-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
@@ -87,7 +85,6 @@ class ConvertCommand : public Command {
std::string output_path_;
std::optional<std::string> output_format_;
bool verbose_ = false;
- bool enable_sparse_encoding_ = false;
bool force_sparse_encoding_ = false;
bool enable_compact_entries_ = false;
std::optional<std::string> resources_config_path_;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 232b4024abd2..eb71189ffc46 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -2504,9 +2504,6 @@ int LinkCommand::Action(const std::vector<std::string>& args) {
<< "the --merge-only flag can be only used when building a static library");
return 1;
}
- if (options_.use_sparse_encoding) {
- options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled;
- }
// The default build type.
context.SetPackageType(PackageType::kApp);
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 2f17853718ec..b5bd905c02be 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -75,7 +75,6 @@ struct LinkOptions {
bool no_resource_removal = false;
bool no_xml_namespaces = false;
bool do_not_compress_anything = false;
- bool use_sparse_encoding = false;
std::unordered_set<std::string> extensions_to_not_compress;
std::optional<std::regex> regex_to_not_compress;
FeatureFlagValues feature_flag_values;
@@ -163,9 +162,11 @@ class LinkCommand : public Command {
AddOptionalSwitch("--no-resource-removal", "Disables automatic removal of resources without\n"
"defaults. Use this only when building runtime resource overlay packages.",
&options_.no_resource_removal);
- AddOptionalSwitch("--enable-sparse-encoding",
- "This decreases APK size at the cost of resource retrieval performance.",
- &options_.use_sparse_encoding);
+ AddOptionalSwitch(
+ "--enable-sparse-encoding",
+ "[DEPRECATED] This flag is a no-op as of aapt2 v2.20. Sparse encoding is always\n"
+ "enabled if minSdk of the APK is >= 32.",
+ nullptr);
AddOptionalSwitch("--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
&options_.table_flattener_options.use_compact_entries);
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 762441ee1872..f218307af578 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -406,9 +406,6 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) {
return 1;
}
- if (options_.enable_sparse_encoding) {
- options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled;
- }
if (options_.force_sparse_encoding) {
options_.table_flattener_options.sparse_entries = SparseEntriesMode::Forced;
}
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 012b0f230ca2..e3af584cbbd9 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -61,9 +61,6 @@ struct OptimizeOptions {
// TODO(b/246489170): keep the old option and format until transform to the new one
std::optional<std::string> shortened_paths_map_path;
- // Whether sparse encoding should be used for O+ resources.
- bool enable_sparse_encoding = false;
-
// Whether sparse encoding should be used for all resources.
bool force_sparse_encoding = false;
@@ -106,11 +103,9 @@ class OptimizeCommand : public Command {
&kept_artifacts_);
AddOptionalSwitch(
"--enable-sparse-encoding",
- "Enables encoding sparse entries using a binary search tree.\n"
- "This decreases APK size at the cost of resource retrieval performance.\n"
- "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
- "the APK is O+",
- &options_.enable_sparse_encoding);
+ "[DEPRECATED] This flag is a no-op as of aapt2 v2.20. Sparse encoding is always\n"
+ "enabled if minSdk of the APK is >= 32.",
+ nullptr);
AddOptionalSwitch("--force-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 1a82021bce71..b8ac7925d44e 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -201,7 +201,7 @@ class PackageFlattener {
(context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) {
// Sparse encode if forced or sdk version is not set in context and config.
} else {
- // Otherwise, only sparse encode if the entries will be read on platforms S_V2+.
+ // Otherwise, only sparse encode if the entries will be read on platforms S_V2+ (32).
sparse_encode = sparse_encode && (context_->GetMinSdkVersion() >= SDK_S_V2);
}
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 0633bc81cb25..f1c4c3512ed3 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -37,8 +37,7 @@ constexpr const size_t kSparseEncodingThreshold = 60;
enum class SparseEntriesMode {
// Disables sparse encoding for entries.
Disabled,
- // Enables sparse encoding for all entries for APKs with O+ minSdk. For APKs with minSdk less
- // than O only applies sparse encoding for resource configuration available on O+.
+ // Enables sparse encoding for all entries for APKs with minSdk >= 32 (S_V2).
Enabled,
// Enables sparse encoding for all entries regardless of minSdk.
Forced,
@@ -47,7 +46,7 @@ enum class SparseEntriesMode {
struct TableFlattenerOptions {
// When enabled, types for configurations with a sparse set of entries are encoded
// as a sparse map of entry ID and offset to actual data.
- SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled;
+ SparseEntriesMode sparse_entries = SparseEntriesMode::Enabled;
// When true, use compact entries for simple data
bool use_compact_entries = false;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 0f1168514c4a..e3d589eb078b 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -337,13 +337,13 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) {
auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
TableFlattenerOptions options;
- options.sparse_entries = SparseEntriesMode::Enabled;
+ options.sparse_entries = SparseEntriesMode::Disabled;
std::string no_sparse_contents;
- ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &no_sparse_contents));
std::string sparse_contents;
- ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &sparse_contents));
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
@@ -421,13 +421,13 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
TableFlattenerOptions options;
- options.sparse_entries = SparseEntriesMode::Enabled;
+ options.sparse_entries = SparseEntriesMode::Disabled;
std::string no_sparse_contents;
- ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &no_sparse_contents));
std::string sparse_contents;
- ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &sparse_contents));
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
diff --git a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
index 6da6fc6f12c3..d0807f2ecd34 100644
--- a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
+++ b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
@@ -877,7 +877,7 @@ resource_table {
}
tool_fingerprint {
tool: "Android Asset Packaging Tool (aapt)"
- version: "2.19-SOONG BUILD NUMBER PLACEHOLDER"
+ version: "2.20-SOONG BUILD NUMBER PLACEHOLDER"
}
}
xml_files {
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 8368f9d16af8..664d8412a3be 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,5 +1,11 @@
# Android Asset Packaging Tool 2.0 (AAPT2) release notes
+## Version 2.20
+- Too many features, bug fixes, and improvements to list since the last minor version update in
+ 2017. This README will be updated more frequently in the future.
+- Sparse encoding is now always enabled by default if the minSdkVersion is >= 32 (S_V2). The
+ `--enable-sparse-encoding` flag still exists, but is a no-op.
+
## Version 2.19
- Added navigation resource type.
- Fixed issue with resource deduplication. (bug 64397629)
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 3d83caf29bba..6a4dfa629394 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -227,7 +227,7 @@ std::string GetToolFingerprint() {
static const char* const sMajorVersion = "2";
// Update minor version whenever a feature or flag is added.
- static const char* const sMinorVersion = "19";
+ static const char* const sMinorVersion = "20";
// The build id of aapt2 binary.
static const std::string sBuildId = [] {