summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl1
-rw-r--r--core/java/android/app/Notification.java100
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java3
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig2
-rw-r--r--core/java/android/content/res/Configuration.java5
-rw-r--r--core/java/android/content/res/ResourcesImpl.java4
-rw-r--r--core/java/android/hardware/display/DisplayManager.java74
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java103
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java2
-rw-r--r--core/java/android/os/vibrator/flags.aconfig10
-rw-r--r--core/java/android/provider/Settings.java10
-rw-r--r--core/java/android/view/Display.java7
-rw-r--r--core/java/android/view/InsetsController.java6
-rw-r--r--core/java/android/view/WindowInsets.java3
-rw-r--r--core/java/android/window/DesktopModeFlags.java1
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig14
-rw-r--r--core/java/com/android/internal/pm/pkg/component/AconfigFlags.java7
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java54
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java8
-rw-r--r--core/java/com/android/internal/widget/MessagingLayout.java98
-rw-r--r--core/jni/android_app_PropertyInvalidatedCache.cpp6
-rw-r--r--core/jni/android_app_PropertyInvalidatedCache.h54
-rw-r--r--core/jni/android_view_SurfaceControl.cpp26
-rw-r--r--core/res/res/layout/notification_2025_conversation_header.xml189
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_call.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_conversation.xml216
-rw-r--r--core/res/res/layout/notification_2025_template_conversation.xml159
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_call.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_conversation.xml75
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_messaging.xml6
-rw-r--r--core/res/res/layout/notification_2025_top_line_views.xml2
-rw-r--r--core/res/res/values/config_telephony.xml7
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java116
-rw-r--r--core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt151
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt168
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt170
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt233
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt9
-rw-r--r--native/android/surface_control.cpp28
-rw-r--r--packages/CtsShim/build/Android.bp30
-rw-r--r--packages/CtsShim/build/shim/AndroidManifest.xml8
-rw-r--r--packages/CtsShim/build/shim/AndroidManifestUpgrade.xml30
-rw-r--r--packages/SettingsLib/BannerMessagePreference/Android.bp1
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml157
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml137
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml8
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java222
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java16
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt196
-rw-r--r--packages/SettingsLib/ButtonPreference/Android.bp1
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt6
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml8
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml8
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml8
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java21
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt44
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt160
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt48
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java)73
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt145
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt54
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java5
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/shared/Android.bp1
-rw-r--r--packages/SystemUI/shared/res/values/bools.xml5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java157
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt22
-rw-r--r--packages/Vcn/framework-b/Android.bp3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java20
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java20
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java6
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java6
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationDiskStore.java42
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationStore.java12
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java21
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java4
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java26
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java3
-rw-r--r--services/core/java/com/android/server/am/UserController.java2
-rw-r--r--services/core/java/com/android/server/am/UserSwitchingDialog.java6
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java8
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java14
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig17
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java25
-rw-r--r--services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java9
-rw-r--r--services/core/java/com/android/server/storage/StorageSessionController.java37
-rw-r--r--services/core/java/com/android/server/vibrator/AbstractVibratorStep.java2
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/ExternalVibrationSession.java2
-rw-r--r--services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java3
-rw-r--r--services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java8
-rw-r--r--services/core/java/com/android/server/vibrator/SingleVibrationSession.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSession.java2
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java35
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorController.java65
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java12
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java87
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java20
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java9
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java80
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java1
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java2
-rw-r--r--services/core/java/com/android/server/wm/PresentationController.java10
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java38
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java32
-rw-r--r--services/core/java/com/android/server/wm/TrustedPresentationListenerController.java12
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java300
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java25
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java28
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java50
-rw-r--r--services/core/jni/Android.bp1
-rw-r--r--services/core/jni/com_android_server_vibrator_VibratorController.cpp56
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java9
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java66
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java102
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java21
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java10
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java293
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java61
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java60
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java106
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java7
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt45
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt130
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java19
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java44
-rw-r--r--tools/aapt2/cmd/Link.cpp4
-rw-r--r--tools/aapt2/cmd/Link.h9
-rw-r--r--tools/aapt2/cmd/Link_test.cpp41
-rw-r--r--tools/aapt2/readme.md2
231 files changed, 5397 insertions, 2460 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 89b377314887..4222c7c64672 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1737,6 +1737,7 @@ package android.hardware.display {
method @NonNull public int[] getUserDisabledHdrTypes();
method public boolean isMinimalPostProcessingRequested(int);
method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void overrideHdrTypes(int, @NonNull int[]);
+ method @FlaggedApi("com.android.server.display.feature.flags.delay_implicit_rr_registration_until_rr_accessed") public void resetImplicitRefreshRateCallbackStatus();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
method @RequiresPermission(android.Manifest.permission.MODIFY_HDR_CONVERSION_MODE) public void setHdrConversionMode(@NonNull android.hardware.display.HdrConversionMode);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 4b1afa517122..01b2953362b5 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -146,6 +146,7 @@ interface IActivityTaskManager {
int getFrontActivityScreenCompatMode();
void setFrontActivityScreenCompatMode(int mode);
void setFocusedTask(int taskId);
+ boolean setTaskIsPerceptible(int taskId, boolean isPerceptible);
boolean removeTask(int taskId);
void removeAllVisibleRecentTasks();
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, boolean filterOnlyVisibleRecents,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5dca1c70a2e6..06047a42e70d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -18,6 +18,7 @@ package android.app;
import static android.annotation.Dimension.DP;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
+import static android.app.Flags.notificationsRedesignTemplates;
import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -818,7 +819,8 @@ public class Notification implements Parcelable
R.layout.notification_2025_template_expanded_base,
R.layout.notification_2025_template_heads_up_base,
R.layout.notification_2025_template_header,
- R.layout.notification_2025_template_conversation,
+ R.layout.notification_2025_template_collapsed_conversation,
+ R.layout.notification_2025_template_expanded_conversation,
R.layout.notification_2025_template_collapsed_call,
R.layout.notification_2025_template_expanded_call,
R.layout.notification_2025_template_collapsed_messaging,
@@ -5963,7 +5965,8 @@ public class Notification implements Parcelable
|| resId == getCompactHeadsUpBaseLayoutResource()
|| resId == getMessagingCompactHeadsUpLayoutResource()
|| resId == getCollapsedMessagingLayoutResource()
- || resId == getCollapsedMediaLayoutResource());
+ || resId == getCollapsedMediaLayoutResource()
+ || resId == getCollapsedConversationLayoutResource());
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
@@ -6001,8 +6004,7 @@ public class Notification implements Parcelable
// Update margins to leave space for the top line (but not for headerless views like
// HUNS, which use a different layout that already accounts for that). Templates that
// have content that will be displayed under the small icon also use a different margin.
- if (Flags.notificationsRedesignTemplates()
- && !p.mHeaderless && !p.mSkipTopLineAlignment) {
+ if (Flags.notificationsRedesignTemplates() && !p.mHeaderless) {
int margin = getContentMarginTop(mContext,
R.dimen.notification_2025_content_margin_top);
contentView.setViewLayoutMargin(R.id.notification_main_column,
@@ -7673,12 +7675,18 @@ public class Notification implements Parcelable
}
}
+ // Note: In the 2025 redesign, we use two separate layouts for the collapsed and expanded
+ // version of conversations. See below.
private int getConversationLayoutResource() {
- if (Flags.notificationsRedesignTemplates()) {
- return R.layout.notification_2025_template_conversation;
- } else {
- return R.layout.notification_template_material_conversation;
- }
+ return R.layout.notification_template_material_conversation;
+ }
+
+ private int getCollapsedConversationLayoutResource() {
+ return R.layout.notification_2025_template_collapsed_conversation;
+ }
+
+ private int getExpandedConversationLayoutResource() {
+ return R.layout.notification_2025_template_expanded_conversation;
}
private int getCollapsedCallLayoutResource() {
@@ -9462,7 +9470,7 @@ public class Notification implements Parcelable
boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
- boolean isHeaderless = !isConversationLayout && isCollapsed;
+ boolean isLegacyHeaderless = !isConversationLayout && isCollapsed;
//TODO (b/217799515): ensure mConversationTitle always returns the correct
// conversationTitle, probably set mConversationTitle = conversationTitle after this
@@ -9483,7 +9491,8 @@ public class Notification implements Parcelable
} else {
isOneToOne = !isGroupConversation();
}
- if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
+ if ((isLegacyHeaderless || notificationsRedesignTemplates())
+ && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
conversationTitle = getOtherPersonName();
}
@@ -9493,22 +9502,24 @@ public class Notification implements Parcelable
.viewType(viewType)
.highlightExpander(isConversationLayout)
.hideProgress(true)
- .title(isHeaderless ? conversationTitle : null)
.text(null)
.hideLeftIcon(isOneToOne)
- .hideRightIcon(hideRightIcons || isOneToOne)
- .headerTextSecondary(isHeaderless ? null : conversationTitle)
- .skipTopLineAlignment(true);
+ .hideRightIcon(hideRightIcons || isOneToOne);
+ if (notificationsRedesignTemplates()) {
+ p.title(conversationTitle)
+ .hideAppName(isCollapsed);
+ } else {
+ p.title(isLegacyHeaderless ? conversationTitle : null)
+ .headerTextSecondary(isLegacyHeaderless ? null : conversationTitle);
+ }
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- isConversationLayout
- ? mBuilder.getConversationLayoutResource()
- : isCollapsed
- ? mBuilder.getCollapsedMessagingLayoutResource()
- : mBuilder.getExpandedMessagingLayoutResource(),
+ getMessagingLayoutResource(isConversationLayout, isCollapsed),
p,
bindResult);
- if (isConversationLayout) {
+ if (isConversationLayout && !notificationsRedesignTemplates()) {
+ // Redesign note: This view is replaced by the `title`, which is handled normally.
mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+ // Redesign note: This special divider is no longer needed.
mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
}
@@ -9538,7 +9549,18 @@ public class Notification implements Parcelable
contentView.setBoolean(R.id.status_bar_latest_event_content,
"setIsImportantConversation", isImportantConversation);
}
- if (isHeaderless) {
+ if (notificationsRedesignTemplates() && !isCollapsed) {
+ // Align the title to the app/small icon in the expanded form. In other layouts,
+ // this margin is added directly to the notification_main_column parent, but for
+ // messages we don't want the margin to be applied to the actual messaging
+ // content since it can contain icons that are displayed below the app icon.
+ Resources res = mBuilder.mContext.getResources();
+ int marginStart = res.getDimensionPixelSize(
+ R.dimen.notification_2025_content_margin_start);
+ contentView.setViewLayoutMargin(R.id.title,
+ RemoteViews.MARGIN_START, marginStart, TypedValue.COMPLEX_UNIT_PX);
+ }
+ if (isLegacyHeaderless) {
// Collapsed legacy messaging style has a 1-line limit.
contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
}
@@ -9549,6 +9571,33 @@ public class Notification implements Parcelable
return contentView;
}
+ private int getMessagingLayoutResource(boolean isConversationLayout, boolean isCollapsed) {
+ if (notificationsRedesignTemplates()) {
+ // Note: We eventually would like to use the same layouts for both conversations and
+ // regular messaging notifications.
+ if (isConversationLayout) {
+ if (isCollapsed) {
+ return mBuilder.getCollapsedConversationLayoutResource();
+ } else {
+ return mBuilder.getExpandedConversationLayoutResource();
+ }
+ } else {
+ if (isCollapsed) {
+ return mBuilder.getCollapsedMessagingLayoutResource();
+ } else {
+ return mBuilder.getExpandedMessagingLayoutResource();
+ }
+ }
+
+ } else {
+ return isConversationLayout
+ ? mBuilder.getConversationLayoutResource()
+ : isCollapsed
+ ? mBuilder.getCollapsedMessagingLayoutResource()
+ : mBuilder.getExpandedMessagingLayoutResource();
+ }
+ }
+
private CharSequence getKey(Person person) {
return person == null ? null
: person.getKey() == null ? person.getName() : person.getKey();
@@ -14676,7 +14725,6 @@ public class Notification implements Parcelable
Icon mPromotedPicture;
boolean mCallStyleActions;
boolean mAllowTextWithProgress;
- boolean mSkipTopLineAlignment;
int mTitleViewId;
int mTextViewId;
@Nullable CharSequence mTitle;
@@ -14702,7 +14750,6 @@ public class Notification implements Parcelable
mPromotedPicture = null;
mCallStyleActions = false;
mAllowTextWithProgress = false;
- mSkipTopLineAlignment = false;
mTitleViewId = R.id.title;
mTextViewId = R.id.text;
mTitle = null;
@@ -14769,11 +14816,6 @@ public class Notification implements Parcelable
return this;
}
- public StandardTemplateParams skipTopLineAlignment(boolean skipTopLineAlignment) {
- mSkipTopLineAlignment = skipTopLineAlignment;
- return this;
- }
-
final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
this.mHideSnoozeButton = hideSnoozeButton;
return this;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 08719fc549f8..500f7585b673 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -14200,6 +14200,9 @@ public class DevicePolicyManager {
* <li>Manifest.permission.ACTIVITY_RECOGNITION</li>
* <li>Manifest.permission.BODY_SENSORS</li>
* </ul>
+ * On devices running {@link android.os.Build.VERSION_CODES#BAKLAVA}, the
+ * {@link android.health.connect.HealthPermissions} are also included in the
+ * restricted list.
* <p>
* A profile owner may not grant these permissions (i.e. call this method with any of the
* permissions listed above and {@code grantState} of {@code #PERMISSION_GRANT_STATE_GRANTED}),
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index bc1f7cea7fce..1de034b23a7a 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -24,7 +24,7 @@ flag {
}
flag {
- name: "contextual_search_window_layer"
+ name: "contextual_search_prevent_self_capture"
namespace: "sysui_integrations"
description: "Identify live contextual search UI to exclude from contextual search screenshot."
bug: "390176823"
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index ef200c328d63..2e0999410483 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2358,8 +2358,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* @param locales The locale list. If null, an empty LocaleList will be assigned.
*/
public void setLocales(@Nullable LocaleList locales) {
+ LocaleList oldList = mLocaleList;
mLocaleList = locales == null ? LocaleList.getEmptyLocaleList() : locales;
locale = mLocaleList.get(0);
+ if (!mLocaleList.equals(oldList)) {
+ Slog.v(TAG, "Updating configuration, locales updated from " + oldList
+ + " to " + mLocaleList);
+ }
setLayoutDirection(locale);
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 96c71765d102..eec30f38b415 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -491,6 +491,9 @@ public class ResourcesImpl {
}
defaultLocale =
adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+ Slog.v(TAG, "Updating configuration, with default locale "
+ + defaultLocale + " and selected locales "
+ + Arrays.toString(selectedLocales));
} else {
String[] availableLocales;
// The LocaleList has changed. We must query the AssetManager's
@@ -526,6 +529,7 @@ public class ResourcesImpl {
for (int i = 0; i < locales.size(); i++) {
selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
}
+ defaultLocale = adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
} else {
selectedLocales = new String[]{
adjustLanguageTag(locales.get(0).toLanguageTag())};
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 7850e377ec4d..92a56fc575e3 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -851,6 +851,12 @@ public final class DisplayManager {
* Registers a display listener to receive notifications about when
* displays are added, removed or changed.
*
+ * Because of the high frequency at which the refresh rate can change, clients will be
+ * registered for refresh rate change callbacks only when they request for refresh rate
+ * data({@link Display#getRefreshRate()}. Or alternately, you can consider using
+ * {@link #registerDisplayListener(Executor, long, DisplayListener)} and explicitly subscribe to
+ * {@link #EVENT_TYPE_DISPLAY_REFRESH_RATE} event
+ *
* We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
* instead to subscribe for explicit events of interest
*
@@ -863,8 +869,8 @@ public final class DisplayManager {
public void registerDisplayListener(DisplayListener listener, Handler handler) {
registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
| EVENT_TYPE_DISPLAY_CHANGED
- | EVENT_TYPE_DISPLAY_REFRESH_RATE
- | EVENT_TYPE_DISPLAY_REMOVED);
+ | EVENT_TYPE_DISPLAY_REMOVED, 0,
+ ActivityThread.currentPackageName(), /* isEventFilterExplicit */ false);
}
/**
@@ -882,9 +888,8 @@ public final class DisplayManager {
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @EventType long eventFilter) {
- mGlobal.registerDisplayListener(listener, handler,
- mGlobal.mapFiltersToInternalEventFlag(eventFilter, 0),
- ActivityThread.currentPackageName());
+ registerDisplayListener(listener, handler, eventFilter, 0,
+ ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
}
/**
@@ -901,9 +906,8 @@ public final class DisplayManager {
@FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
public void registerDisplayListener(@NonNull Executor executor, @EventType long eventFilter,
@NonNull DisplayListener listener) {
- mGlobal.registerDisplayListener(listener, executor,
- mGlobal.mapFiltersToInternalEventFlag(eventFilter, 0),
- ActivityThread.currentPackageName());
+ registerDisplayListener(listener, executor, eventFilter, 0,
+ ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
}
/**
@@ -924,9 +928,39 @@ public final class DisplayManager {
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @EventType long eventFilter,
@PrivateEventType long privateEventFilter) {
+ registerDisplayListener(listener, handler, eventFilter, privateEventFilter,
+ ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
+ }
+
+ /**
+ * Registers a display listener to receive notifications about given display event types.
+ *
+ * @param listener The listener to register.
+ * @param handler The handler on which the listener should be invoked, or null
+ * if the listener should be invoked on the calling thread's looper.
+ * @param eventFilter A bitmask of the event types for which this listener is subscribed.
+ * @param privateEventFilter A bitmask of the private event types for which this listener
+ * is subscribed.
+ * @param isEventFilterExplicit Indicates if the client explicitly supplied the display events
+ * to be subscribed to.
+ *
+ */
+ private void registerDisplayListener(@NonNull DisplayListener listener,
+ @Nullable Handler handler, @EventType long eventFilter,
+ @PrivateEventType long privateEventFilter, String packageName,
+ boolean isEventFilterExplicit) {
mGlobal.registerDisplayListener(listener, handler,
mGlobal.mapFiltersToInternalEventFlag(eventFilter, privateEventFilter),
- ActivityThread.currentPackageName());
+ packageName, /* isEventFilterExplicit */ isEventFilterExplicit);
+ }
+
+ private void registerDisplayListener(@NonNull DisplayListener listener,
+ Executor executor, @EventType long eventFilter,
+ @PrivateEventType long privateEventFilter, String packageName,
+ boolean isEventFilterExplicit) {
+ mGlobal.registerDisplayListener(listener, executor,
+ mGlobal.mapFiltersToInternalEventFlag(eventFilter, privateEventFilter),
+ packageName, /* isEventFilterExplicit */ isEventFilterExplicit);
}
/**
@@ -1146,6 +1180,28 @@ public final class DisplayManager {
}
/**
+ * Resets the behavior that automatically registers clients for refresh rate change callbacks
+ * when they register via {@link #registerDisplayListener(DisplayListener, Handler)}
+ *
+ * <p>By default, clients are not registered for refresh rate change callbacks via
+ * {@link #registerDisplayListener(DisplayListener, Handler)}. However, calling
+ * {@link Display#getRefreshRate()} triggers automatic registration for existing and future
+ * {@link DisplayListener} instances. This method reverts this behavior, preventing new
+ * clients from being automatically registered for refresh rate change callbacks. Note that the
+ * existing ones will continue to stay registered
+ *
+ * <p>In essence, this method returns the system to its initial state, where explicit calls to
+ * {{@link Display#getRefreshRate()} are required to receive refresh rate change notifications.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+ @TestApi
+ public void resetImplicitRefreshRateCallbackStatus() {
+ mGlobal.resetImplicitRefreshRateCallbackStatus();
+ }
+
+ /**
* Overrides HDR modes for a display device.
*
* @hide
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a7d610e54e2c..c4af87116eed 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -187,6 +187,9 @@ public final class DisplayManagerGlobal {
private final Binder mToken = new Binder();
+ // Guarded by mLock
+ private boolean mShouldImplicitlyRegisterRrChanges = false;
+
@VisibleForTesting
public DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
@@ -390,27 +393,49 @@ public final class DisplayManagerGlobal {
* the handler for the main thread.
* If that is still null, a runtime exception will be thrown.
* @param packageName of the calling package.
+ * @param isEventFilterExplicit Indicates if the client explicitly supplied the display events
+ * to be subscribed to.
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask,
- String packageName) {
+ String packageName, boolean isEventFilterExplicit) {
Looper looper = getLooperForHandler(handler);
Handler springBoard = new Handler(looper);
registerDisplayListener(listener, new HandlerExecutor(springBoard), internalEventFlagsMask,
- packageName);
+ packageName, isEventFilterExplicit);
}
/**
* Register a listener for display-related changes.
*
* @param listener The listener that will be called when display changes occur.
+ * @param handler Handler for the thread that will be receiving the callbacks. May be null.
+ * If null, listener will use the handler for the current thread, and if still null,
+ * the handler for the main thread.
+ * If that is still null, a runtime exception will be thrown.
+ * @param internalEventFlagsMask Mask of events to be listened to.
+ * @param packageName of the calling package.
+ */
+ public void registerDisplayListener(@NonNull DisplayListener listener,
+ @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask,
+ String packageName) {
+ registerDisplayListener(listener, handler, internalEventFlagsMask, packageName, true);
+ }
+
+
+ /**
+ * Register a listener for display-related changes.
+ *
+ * @param listener The listener that will be called when display changes occur.
* @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
* @param internalEventFlagsMask Mask of events to be listened to.
* @param packageName of the calling package.
+ * @param isEventFilterExplicit Indicates if the explicit events to be subscribed to
+ * were supplied or not
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@NonNull Executor executor, @InternalEventFlag long internalEventFlagsMask,
- String packageName) {
+ String packageName, boolean isEventFilterExplicit) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
@@ -429,7 +454,7 @@ public final class DisplayManagerGlobal {
int index = findDisplayListenerLocked(listener);
if (index < 0) {
mDisplayListeners.add(new DisplayListenerDelegate(listener, executor,
- internalEventFlagsMask, packageName));
+ internalEventFlagsMask, packageName, isEventFilterExplicit));
registerCallbackIfNeededLocked();
} else {
mDisplayListeners.get(index).setEventsMask(internalEventFlagsMask);
@@ -439,6 +464,22 @@ public final class DisplayManagerGlobal {
}
}
+
+ /**
+ * Registers all the clients to INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE events if qualified
+ */
+ public void registerForRefreshRateChanges() {
+ if (!Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (!mShouldImplicitlyRegisterRrChanges) {
+ mShouldImplicitlyRegisterRrChanges = true;
+ updateCallbackIfNeededLocked();
+ }
+ }
+ }
+
public void unregisterDisplayListener(DisplayListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
@@ -521,8 +562,14 @@ public final class DisplayManagerGlobal {
long mask = 0;
final int numListeners = mDisplayListeners.size();
for (int i = 0; i < numListeners; i++) {
- mask |= mDisplayListeners.get(i).mInternalEventFlagsMask;
+ DisplayListenerDelegate displayListenerDelegate = mDisplayListeners.get(i);
+ if (!Flags.delayImplicitRrRegistrationUntilRrAccessed()
+ || mShouldImplicitlyRegisterRrChanges) {
+ displayListenerDelegate.implicitlyRegisterForRRChanges();
+ }
+ mask |= displayListenerDelegate.mInternalEventFlagsMask;
}
+
if (mDispatchNativeCallbacks) {
mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED
| INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
@@ -802,6 +849,18 @@ public final class DisplayManagerGlobal {
}
/**
+ * Resets the implicit registration of refresh rate change callbacks
+ *
+ */
+ public void resetImplicitRefreshRateCallbackStatus() {
+ if (Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+ synchronized (mLock) {
+ mShouldImplicitlyRegisterRrChanges = false;
+ }
+ }
+ }
+
+ /**
* Overrides HDR modes for a display device.
*
*/
@@ -1439,21 +1498,27 @@ public final class DisplayManagerGlobal {
}
}
- private static final class DisplayListenerDelegate {
+ @VisibleForTesting
+ static final class DisplayListenerDelegate {
public final DisplayListener mListener;
public volatile long mInternalEventFlagsMask;
+ // Indicates if the client explicitly supplied the display events to be subscribed to.
+ private final boolean mIsEventFilterExplicit;
+
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Executor mExecutor;
private AtomicLong mGenerationId = new AtomicLong(1);
private final String mPackageName;
DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
- @InternalEventFlag long internalEventFlag, String packageName) {
+ @InternalEventFlag long internalEventFlag, String packageName,
+ boolean isEventFilterExplicit) {
mExecutor = executor;
mListener = listener;
mInternalEventFlagsMask = internalEventFlag;
mPackageName = packageName;
+ mIsEventFilterExplicit = isEventFilterExplicit;
}
void sendDisplayEvent(int displayId, @DisplayEvent int event, @Nullable DisplayInfo info,
@@ -1470,6 +1535,11 @@ public final class DisplayManagerGlobal {
});
}
+ @VisibleForTesting
+ boolean isEventFilterExplicit() {
+ return mIsEventFilterExplicit;
+ }
+
void clearEvents() {
mGenerationId.incrementAndGet();
}
@@ -1478,6 +1548,17 @@ public final class DisplayManagerGlobal {
mInternalEventFlagsMask = newInternalEventFlagsMask;
}
+ private void implicitlyRegisterForRRChanges() {
+ // For backward compatibility, if the user didn't supply the explicit events while
+ // subscribing, register them to refresh rate change events if they subscribed to
+ // display changed events
+ if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) != 0
+ && !mIsEventFilterExplicit) {
+ setEventsMask(mInternalEventFlagsMask
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE);
+ }
+ }
+
private void handleDisplayEventInner(int displayId, @DisplayEvent int event,
@Nullable DisplayInfo info, boolean forceUpdate) {
if (extraLogging()) {
@@ -1677,6 +1758,9 @@ public final class DisplayManagerGlobal {
public void registerNativeChoreographerForRefreshRateCallbacks() {
synchronized (mLock) {
mDispatchNativeCallbacks = true;
+ if (Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+ mShouldImplicitlyRegisterRrChanges = true;
+ }
registerCallbackIfNeededLocked();
updateCallbackIfNeededLocked();
DisplayInfo display = getDisplayInfoLocked(Display.DEFAULT_DISPLAY);
@@ -1806,4 +1890,9 @@ public final class DisplayManagerGlobal {
return baseEventMask;
}
+
+ @VisibleForTesting
+ CopyOnWriteArrayList<DisplayListenerDelegate> getDisplayListeners() {
+ return mDisplayListeners;
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index a528ba4b16bf..7b47efd47008 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -990,6 +990,8 @@ public class InputMethodService extends AbstractInputMethodService {
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // After the IME window was hidden, we can remove its surface
+ scheduleImeSurfaceRemoval();
// The hide request first finishes the animation and then proceeds to the server
// side, finally reaching here, marking this the end state.
ImeTracker.forLogging().onHidden(statsToken);
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 7ceb948945fd..0615578935a1 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -145,3 +145,13 @@ flag {
purpose: PURPOSE_FEATURE
}
}
+
+flag {
+ namespace: "haptics"
+ name: "fix_vibration_thread_callback_handling"
+ description: "Fix how the VibrationThread handles late callbacks from the vibrator HAL"
+ bug: "395005081"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4ebfe53cab58..538283e10738 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13017,18 +13017,16 @@ public final class Settings {
* false/0.
* @hide
*/
- @Readable
public static final String REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI =
"redact_otp_on_wifi";
/**
- * Toggle for whether to immediately redact OTP notifications, or require the device to be
- * locked for 10 minutes. Defaults to false/0
+ * Time (in milliseconds) that the device should need to be locked, in order for an OTP
+ * notification to be redacted. Default is 10 minutes (600,000 ms)
* @hide
*/
- @Readable
- public static final String REDACT_OTP_NOTIFICATION_IMMEDIATELY =
- "remove_otp_redaction_delay";
+ public static final String OTP_NOTIFICATION_REDACTION_LOCK_TIME =
+ "otp_redaction_lock_time";
/**
* These entries are considered common between the personal and the managed profile,
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 231aa6816908..3f45e29f2d43 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -108,6 +108,7 @@ public final class Display {
private final String mOwnerPackageName;
private final Resources mResources;
private DisplayAdjustments mDisplayAdjustments;
+ private boolean mRefreshRateChangesRegistered;
@UnsupportedAppUsage
private DisplayInfo mDisplayInfo; // never null
@@ -1217,6 +1218,10 @@ public final class Display {
*/
public float getRefreshRate() {
synchronized (mLock) {
+ if (!mRefreshRateChangesRegistered) {
+ DisplayManagerGlobal.getInstance().registerForRefreshRateChanges();
+ mRefreshRateChangesRegistered = true;
+ }
updateDisplayInfoLocked();
return mDisplayInfo.getRefreshRate();
}
@@ -1601,7 +1606,7 @@ public final class Display {
.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
| DisplayManagerGlobal
.INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
- ActivityThread.currentPackageName());
+ ActivityThread.currentPackageName(), /* isEventFilterImplicit */ true);
}
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c174fbe0bbcd..3659e785f590 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1299,7 +1299,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// Handle the pending show request for other insets types since the IME insets
// has being requested hidden.
handlePendingControlRequest(statsToken);
- getImeSourceConsumer().removeSurface();
+ if (!Flags.refactorInsetsController()) {
+ // the surface can't be removed until the end of the animation. This is handled by
+ // IMMS after the window was requested to be hidden.
+ getImeSourceConsumer().removeSurface();
+ }
}
applyAnimation(typesReady, false /* show */, fromIme, false /* skipsCallbacks */,
statsToken);
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index e3ea6b229d64..a952967f4daf 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1290,7 +1290,8 @@ public final class WindowInsets {
int newTop = Math.max(0, insets.top - top);
int newRight = Math.max(0, insets.right - right);
int newBottom = Math.max(0, insets.bottom - bottom);
- if (newLeft == left && newTop == top && newRight == right && newBottom == bottom) {
+ if (newLeft == insets.left && newTop == insets.top
+ && newRight == insets.right && newBottom == insets.bottom) {
return insets;
}
return Insets.of(newLeft, newTop, newRight, newBottom);
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index b44620f12bbb..4ba97384192f 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -105,6 +105,7 @@ public enum DesktopModeFlags {
ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true),
+ ENABLE_TASKBAR_OVERFLOW(Flags::enableTaskbarOverflow, false),
ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true),
ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 3e3b8e100d6d..8265d2523d6f 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -705,6 +705,13 @@ flag {
}
flag {
+ name: "enable_activity_embedding_support_for_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enables activity embedding support for connected displays, including enabling AE optimization for Settings."
+ bug: "369438353"
+}
+
+flag {
name: "enable_full_screen_window_on_removing_split_screen_stage_bugfix"
namespace: "lse_desktop_experience"
description: "Enables clearing the windowing mode of a freeform window when removing the task from the split screen stage."
@@ -764,3 +771,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_taskbar_overflow"
+ namespace: "lse_desktop_experience"
+ description: "Show recent apps in the taskbar overflow."
+ bug: "368119679"
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index b57acf3d97fd..b19967a47001 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -261,7 +261,12 @@ public class AconfigFlags {
// Default value is false for when the flag is not found.
// Note: Unlike with the old storage, with AconfigPackage, we don't have a way to
// know if the flag is not found or if it's found but the value is false.
- value = aconfigPackage.getBooleanFlagValue(flagName, false);
+ try {
+ value = aconfigPackage.getBooleanFlagValue(flagName, false);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Failed to read Aconfig flag value for " + flagPackageAndName, e);
+ return null;
+ }
}
if (DEBUG) {
Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 641ecc9b675a..ce46da12aa76 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -250,7 +250,8 @@ public class ConversationLayout extends FrameLayout
mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
});
- mConversationText = findViewById(R.id.conversation_text);
+ mConversationText = findViewById(notificationsRedesignTemplates()
+ ? R.id.title : R.id.conversation_text);
mExpandButtonContainer = findViewById(R.id.expand_button_container);
mExpandButtonContainerA11yContainer =
findViewById(R.id.expand_button_a11y_container);
@@ -716,17 +717,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateImageMessages() {
- View newMessage = null;
- if (mIsCollapsed && !mGroups.isEmpty()) {
-
- // When collapsed, we're displaying the image message in a dedicated container
- // on the right of the layout instead of inline. Let's add the isolated image there
- MessagingGroup messagingGroup = mGroups.getLast();
- MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
- if (isolatedMessage != null) {
- newMessage = isolatedMessage.getView();
- }
+ if (mImageMessageContainer == null) {
+ return;
}
+ View newMessage = getNewImageMessage();
// Remove all messages that don't belong into the image layout
View previousMessage = mImageMessageContainer.getChildAt(0);
if (previousMessage != newMessage) {
@@ -738,6 +732,20 @@ public class ConversationLayout extends FrameLayout
mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE);
}
+ @Nullable
+ private View getNewImageMessage() {
+ if (mIsCollapsed && !mGroups.isEmpty()) {
+ // When collapsed, we're displaying the image message in a dedicated container
+ // on the right of the layout instead of inline. Let's add the isolated image there
+ MessagingGroup messagingGroup = mGroups.getLast();
+ MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ return isolatedMessage.getView();
+ }
+ }
+ return null;
+ }
+
public void bindFacePile(ImageView bottomBackground, ImageView bottomView, ImageView topView) {
applyNotificationBackgroundColor(bottomBackground);
// Let's find the two last conversations:
@@ -841,6 +849,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateAppName() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
mAppName.setVisibility(mIsCollapsed ? GONE : VISIBLE);
}
@@ -1533,6 +1545,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateExpandButton() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
int buttonGravity;
ViewGroup newContainer;
if (mIsCollapsed) {
@@ -1565,6 +1581,10 @@ public class ConversationLayout extends FrameLayout
}
private void updateContentEndPaddings() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
// Let's make sure the conversation header can't run into the expand button when we're
// collapsed and update the paddings of the content
int headerPaddingEnd;
@@ -1593,6 +1613,10 @@ public class ConversationLayout extends FrameLayout
}
private void onAppNameVisibilityChanged() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
boolean appNameGone = mAppName.getVisibility() == GONE;
if (appNameGone != mAppNameGone) {
mAppNameGone = appNameGone;
@@ -1601,10 +1625,18 @@ public class ConversationLayout extends FrameLayout
}
private void updateAppNameDividerVisibility() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
mAppNameDivider.setVisibility(mAppNameGone ? GONE : VISIBLE);
}
public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
+
mExpandable = expandable;
if (expandable) {
mExpandButtonContainer.setVisibility(VISIBLE);
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 31d9770f6ac4..b9a603cc5696 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -449,12 +449,8 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
}
private void updateIconVisibility() {
- if (Flags.notificationsRedesignTemplates() && !mIsInConversation) {
- // We don't show any icon (other than the app icon) in the collapsed form. For
- // conversations, keeping this container helps with aligning the message to the icon
- // when collapsed, but the old messaging style already has this alignment built into
- // the template like all other layouts. Conversations are special because we use the
- // same base layout for both the collapsed and expanded views.
+ if (Flags.notificationsRedesignTemplates()) {
+ // We don't show any icon (other than the app or person icon) in the collapsed form.
mMessagingIconContainer.setVisibility(mSingleLine ? GONE : VISIBLE);
}
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index e9d920ca3fcd..eb22e7c8cdc0 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -121,22 +121,38 @@ public class MessagingLayout extends FrameLayout
setMessagingClippingDisabled(false);
}
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setAvatarReplacementAsync")
public void setAvatarReplacement(Icon icon) {
mAvatarReplacement = icon;
}
- @RemotableViewMethod
+ /**
+ * @hide
+ */
+ public Runnable setAvatarReplacementAsync(Icon icon) {
+ mAvatarReplacement = icon;
+ return () -> {};
+ }
+
+ @RemotableViewMethod(asyncImpl = "setNameReplacementAsync")
public void setNameReplacement(CharSequence nameReplacement) {
mNameReplacement = nameReplacement;
}
/**
+ * @hide
+ */
+ public Runnable setNameReplacementAsync(CharSequence nameReplacement) {
+ mNameReplacement = nameReplacement;
+ return () -> {};
+ }
+
+ /**
* Set this layout to show the collapsed representation.
*
* @param isCollapsed is it collapsed
*/
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
}
@@ -145,7 +161,6 @@ public class MessagingLayout extends FrameLayout
* setDataAsync needs to do different stuff for the collapsed vs expanded view, so store the
* collapsed state early.
*/
- @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
public Runnable setIsCollapsedAsync(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
return () -> {};
@@ -161,12 +176,20 @@ public class MessagingLayout extends FrameLayout
*
* @param conversationTitle the conversation title
*/
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setConversationTitleAsync")
public void setConversationTitle(CharSequence conversationTitle) {
mConversationTitle = conversationTitle;
}
/**
+ * @hide
+ */
+ public Runnable setConversationTitleAsync(CharSequence conversationTitle) {
+ mConversationTitle = conversationTitle;
+ return ()->{};
+ }
+
+ /**
* Set Messaging data
* @param extras Bundle contains messaging data
*/
@@ -314,19 +337,10 @@ public class MessagingLayout extends FrameLayout
}
private void updateImageMessages() {
- View newMessage = null;
if (mImageMessageContainer == null) {
return;
}
- if (mIsCollapsed && !mGroups.isEmpty()) {
- // When collapsed, we're displaying the image message in a dedicated container
- // on the right of the layout instead of inline. Let's add the isolated image there
- MessagingGroup messagingGroup = mGroups.getLast();
- MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
- if (isolatedMessage != null) {
- newMessage = isolatedMessage.getView();
- }
- }
+ View newMessage = getNewImageMessage();
// Remove all messages that don't belong into the image layout
View previousMessage = mImageMessageContainer.getChildAt(0);
if (previousMessage != newMessage) {
@@ -345,6 +359,20 @@ public class MessagingLayout extends FrameLayout
}
}
+ @Nullable
+ private View getNewImageMessage() {
+ if (mIsCollapsed && !mGroups.isEmpty()) {
+ // When collapsed, we're displaying the image message in a dedicated container
+ // on the right of the layout instead of inline. Let's add the isolated image there
+ MessagingGroup messagingGroup = mGroups.getLast();
+ MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ return isolatedMessage.getView();
+ }
+ }
+ return null;
+ }
+
private void removeGroups(ArrayList<MessagingGroup> oldGroups) {
int size = oldGroups.size();
for (int i = 0; i < size; i++) {
@@ -417,22 +445,44 @@ public class MessagingLayout extends FrameLayout
return mPeopleHelper.createAvatarSymbol(senderName, symbol, layoutColor);
}
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setLayoutColorAsync")
public void setLayoutColor(int color) {
mLayoutColor = color;
}
- @RemotableViewMethod
+ /**
+ * @hide
+ */
+ public Runnable setLayoutColorAsync(int color) {
+ mLayoutColor = color;
+ return () -> {};
+ }
+
+ @RemotableViewMethod(asyncImpl = "setIsOneToOneAsync")
public void setIsOneToOne(boolean oneToOne) {
mIsOneToOne = oneToOne;
}
- @RemotableViewMethod
+ /**
+ * @hide
+ */
+ public Runnable setIsOneToOneAsync(boolean oneToOne) {
+ mIsOneToOne = oneToOne;
+ return () -> {};
+ }
+
+ @RemotableViewMethod(asyncImpl = "setSenderTextColorAsync")
public void setSenderTextColor(int color) {
mSenderTextColor = color;
}
-
+ /**
+ * @hide
+ */
+ public Runnable setSenderTextColorAsync(int color) {
+ mSenderTextColor = color;
+ return () -> {};
+ }
/**
* @param color the color of the notification background
*/
@@ -441,11 +491,19 @@ public class MessagingLayout extends FrameLayout
// Nothing to do with this
}
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setMessageTextColorAsync")
public void setMessageTextColor(int color) {
mMessageTextColor = color;
}
+ /**
+ * @hide
+ */
+ public Runnable setMessageTextColorAsync(int color) {
+ mMessageTextColor = color;
+ return () -> {};
+ }
+
public void setUser(Person user) {
mUser = user;
if (mUser.getIcon() == null) {
diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp
index 12585d5f8137..6267522f114f 100644
--- a/core/jni/android_app_PropertyInvalidatedCache.cpp
+++ b/core/jni/android_app_PropertyInvalidatedCache.cpp
@@ -35,7 +35,7 @@ int NonceStore::getMaxNonce() const {
return kMaxNonce;
}
-size_t NonceStore::getMaxByte() const {
+int32_t NonceStore::getMaxByte() const {
return kMaxByte;
}
@@ -68,13 +68,13 @@ int32_t NonceStore::getHash() const {
}
// Copy the byte block to the target and return the current hash.
-int32_t NonceStore::getByteBlock(block_t* block, size_t len) const {
+int32_t NonceStore::getByteBlock(block_t* block, int32_t len) const {
memcpy(block, (void*) byteBlock(), std::min(kMaxByte, len));
return mByteHash;
}
// Set the byte block and the hash.
-void NonceStore::setByteBlock(int hash, const block_t* block, size_t len) {
+void NonceStore::setByteBlock(int hash, const block_t* block, int32_t len) {
memcpy((void*) byteBlock(), block, len = std::min(kMaxByte, len));
mByteHash = hash;
}
diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h
index 54a4ac65fce2..1d75182356c6 100644
--- a/core/jni/android_app_PropertyInvalidatedCache.h
+++ b/core/jni/android_app_PropertyInvalidatedCache.h
@@ -27,8 +27,12 @@ namespace android::app::PropertyInvalidatedCache {
* location. Fields with a variable location are found via offsets. The offsets make this
* object position-independent, which is required because it is in shared memory and would be
* mapped into different virtual addresses for different processes.
+ *
+ * This structure is shared between 64-bit and 32-bit processes. Therefore it is imperative
+ * that the structure not use any datatypes that are architecture-dependent (like size_t).
+ * Additionally, care must be taken to avoid unexpected padding in the structure.
*/
-class NonceStore {
+class alignas(8) NonceStore {
protected:
// A convenient typedef. The jbyteArray element type is jbyte, which the compiler treats as
// signed char.
@@ -43,23 +47,27 @@ class NonceStore {
// The value of an unset field.
static constexpr int UNSET = 0;
- // The size of the nonce array.
+ // The size of the nonce array. This and the following sizes are int32_t to
+ // be ABI independent.
const int32_t kMaxNonce;
// The size of the byte array.
- const size_t kMaxByte;
+ const int32_t kMaxByte;
// The offset to the nonce array.
- const size_t mNonceOffset;
+ const int32_t mNonceOffset;
// The offset to the byte array.
- const size_t mByteOffset;
+ const int32_t mByteOffset;
// The byte block hash. This is fixed and at a known offset, so leave it in the base class.
volatile std::atomic<int32_t> mByteHash;
+ // A 4-byte padd that makes the size of this structure a multiple of 8 bytes.
+ const int32_t _pad = 0;
+
// The constructor is protected! It only makes sense when called from a subclass.
- NonceStore(int kMaxNonce, size_t kMaxByte, volatile nonce_t* nonce, volatile block_t* block) :
+ NonceStore(int kMaxNonce, int kMaxByte, volatile nonce_t* nonce, volatile block_t* block) :
kMaxNonce(kMaxNonce),
kMaxByte(kMaxByte),
mNonceOffset(offset(this, const_cast<nonce_t*>(nonce))),
@@ -70,7 +78,7 @@ class NonceStore {
// These provide run-time access to the sizing parameters.
int getMaxNonce() const;
- size_t getMaxByte() const;
+ int getMaxByte() const;
// Fetch a nonce, returning UNSET if the index is out of range. This method specifically
// does not throw or generate an error if the index is out of range; this allows the method
@@ -86,10 +94,10 @@ class NonceStore {
int32_t getHash() const;
// Copy the byte block to the target and return the current hash.
- int32_t getByteBlock(block_t* block, size_t len) const;
+ int32_t getByteBlock(block_t* block, int32_t len) const;
// Set the byte block and the hash.
- void setByteBlock(int hash, const block_t* block, size_t len);
+ void setByteBlock(int hash, const block_t* block, int32_t len);
private:
@@ -113,6 +121,12 @@ class NonceStore {
}
};
+// Assert that the size of the object is fixed, independent of the CPU architecture. There are
+// four int32_t fields and one atomic<int32_t>, which sums to 20 bytes total. This assertion
+// uses a constant instead of computing the size of the objects in the compiler, to avoid
+// different answers on different architectures.
+static_assert(sizeof(NonceStore) == 24);
+
/**
* A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes. The
* byte array has an associated hash. This class provides methods to read and write the fields
@@ -126,20 +140,22 @@ class NonceStore {
* The template is parameterized by the number of nonces it supports and the number of bytes in
* the string block.
*/
-template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore {
+template<int MAX_NONCE, int MAX_BYTE> class CacheNonce : public NonceStore {
// The array of nonces
- volatile nonce_t mNonce[maxNonce];
+ volatile nonce_t mNonce[MAX_NONCE];
// The byte array. This is not atomic but it is guarded by the mByteHash.
- volatile block_t mByteBlock[maxByte];
+ volatile block_t mByteBlock[MAX_BYTE];
public:
+ // Export the parameters for use in compiler assertions.
+ static constexpr int kMaxNonceCount = MAX_NONCE;
+ static constexpr int kMaxByteCount = MAX_BYTE;
+
// Construct and initialize the memory.
- CacheNonce() :
- NonceStore(maxNonce, maxByte, &mNonce[0], &mByteBlock[0])
- {
- for (int i = 0; i < maxNonce; i++) {
+ CacheNonce() : NonceStore(MAX_NONCE, MAX_BYTE, &mNonce[0], &mByteBlock[0]) {
+ for (int i = 0; i < MAX_NONCE; i++) {
mNonce[i] = UNSET;
}
mByteHash = UNSET;
@@ -155,4 +171,10 @@ template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore {
typedef CacheNonce</* max nonce */ 128, /* byte block size */ 8192> SystemCacheNonce;
// LINT.ThenChange(/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java:system_nonce_config)
+// Verify that there is no padding in the final class.
+static_assert(sizeof(SystemCacheNonce) ==
+ sizeof(NonceStore)
+ + SystemCacheNonce::kMaxNonceCount*8
+ + SystemCacheNonce::kMaxByteCount);
+
} // namespace android.app.PropertyInvalidatedCache
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b99b0ef7f24e..a8e51a7c1fe8 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -765,28 +765,28 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong
std::vector<int32_t> dimensions;
std::vector<int32_t> sizes;
std::vector<int32_t> samplingKeys;
- int32_t fd = -1;
+ base::unique_fd fd;
if (jdimensionArray) {
jsize numLuts = env->GetArrayLength(jdimensionArray);
- ScopedIntArrayRW joffsets(env, joffsetArray);
+ ScopedIntArrayRO joffsets(env, joffsetArray);
if (joffsets.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from joffsetArray");
return;
}
- ScopedIntArrayRW jdimensions(env, jdimensionArray);
+ ScopedIntArrayRO jdimensions(env, jdimensionArray);
if (jdimensions.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jdimensionArray");
return;
}
- ScopedIntArrayRW jsizes(env, jsizeArray);
+ ScopedIntArrayRO jsizes(env, jsizeArray);
if (jsizes.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsizeArray");
return;
}
- ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
+ ScopedIntArrayRO jsamplingKeys(env, jsamplingKeyArray);
if (jsamplingKeys.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsamplingKeyArray");
return;
}
@@ -796,15 +796,15 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong
sizes = std::vector<int32_t>(jsizes.get(), jsizes.get() + numLuts);
samplingKeys = std::vector<int32_t>(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
- ScopedFloatArrayRW jbuffers(env, jbufferArray);
+ ScopedFloatArrayRO jbuffers(env, jbufferArray);
if (jbuffers.get() == nullptr) {
- jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
+ jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRO from jbufferArray");
return;
}
// create the shared memory and copy jbuffers
size_t bufferSize = jbuffers.size() * sizeof(float);
- fd = ashmem_create_region("lut_shared_mem", bufferSize);
+ fd.reset(ashmem_create_region("lut_shared_mem", bufferSize));
if (fd < 0) {
jniThrowRuntimeException(env, "ashmem_create_region() failed");
return;
@@ -820,7 +820,7 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong
}
}
- transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys);
+ transaction->setLuts(ctrl, std::move(fd), offsets, dimensions, sizes, samplingKeys);
}
static void nativeSetPictureProfileId(JNIEnv* env, jclass clazz, jlong transactionObj,
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 1bde17358825..68096f8cc50e 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2024 The Android Open Source Project
+ ~ Copyright (C) 2025 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,157 +15,74 @@
~ limitations under the License
-->
-<com.android.internal.widget.ConversationHeaderLinearLayout
+<!-- extends RelativeLayout -->
+<NotificationHeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/conversation_header"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:id="@+id/notification_header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_2025_header_height"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
android:orientation="horizontal"
- android:paddingTop="@dimen/notification_2025_margin"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no"
>
- <TextView
- android:id="@+id/conversation_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="16sp"
- android:singleLine="true"
- android:layout_weight="1"
- />
-
- <TextView
- android:id="@+id/app_name_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_2025_left_icon_size"
+ android:layout_height="@dimen/notification_2025_left_icon_size"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
android:visibility="gone"
/>
- <!-- App Name -->
- <com.android.internal.widget.ObservableTextView
- android:id="@+id/app_name_text"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:singleLine="true"
- android:visibility="gone"
- />
+ <include layout="@layout/notification_2025_conversation_icon_container" />
- <TextView
- android:id="@+id/time_divider"
+ <!-- extends ViewGroup -->
+ <NotificationTopLineView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
- android:visibility="gone"
- />
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@id/expand_button"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_marginVertical="@dimen/notification_2025_margin"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/notification_2025_content_margin_start"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
- <DateTimeView
- android:id="@+id/time"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:showRelative="true"
- android:singleLine="true"
- android:visibility="gone"
- />
+ <include layout="@layout/notification_2025_top_line_views" />
- <ViewStub
- android:id="@+id/chronometer"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout="@layout/notification_template_part_chronometer"
- android:visibility="gone"
- />
+ </NotificationTopLineView>
- <TextView
- android:id="@+id/verification_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
- android:visibility="gone"
- />
-
- <ImageView
- android:id="@+id/verification_icon"
- android:layout_width="@dimen/notification_verification_icon_size"
- android:layout_height="@dimen/notification_verification_icon_size"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:baseline="10dp"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_notifications_alerted"
- android:visibility="gone"
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_2025_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ android:importantForAccessibility="no"
+ android:focusable="false"
/>
- <TextView
- android:id="@+id/verification_text"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+ <include layout="@layout/notification_2025_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:layout_weight="100"
- android:showRelative="true"
- android:singleLine="true"
- android:visibility="gone"
- />
+ android:layout_gravity="top|end"
+ android:layout_alignParentEnd="true" />
- <ImageButton
- android:id="@+id/feedback"
- android:layout_width="@dimen/notification_feedback_size"
- android:layout_height="@dimen/notification_feedback_size"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:background="?android:selectableItemBackgroundBorderless"
- android:contentDescription="@string/notification_feedback_indicator"
- android:baseline="13dp"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_feedback_indicator"
- android:visibility="gone"
- />
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true" />
- <ImageView
- android:id="@+id/phishing_alert"
- android:layout_width="@dimen/notification_2025_badge_size"
- android:layout_height="@dimen/notification_2025_badge_size"
- android:layout_marginStart="@dimen/notification_2025_badge_margin"
- android:baseline="@dimen/notification_2025_badge_baseline"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_dialog_alert_material"
- android:visibility="gone"
- android:contentDescription="@string/notification_phishing_alert_content_description"
- />
-
- <ImageView
- android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_2025_badge_size"
- android:layout_height="@dimen/notification_2025_badge_size"
- android:layout_marginStart="@dimen/notification_2025_badge_margin"
- android:baseline="@dimen/notification_2025_badge_baseline"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
-
- <ImageView
- android:id="@+id/alerted_icon"
- android:layout_width="@dimen/notification_2025_badge_size"
- android:layout_height="@dimen/notification_2025_badge_size"
- android:layout_marginStart="@dimen/notification_2025_badge_margin"
- android:baseline="@dimen/notification_2025_badge_baseline"
- android:contentDescription="@string/notification_alerted_content_description"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_notifications_alerted"
- android:visibility="gone"
- />
-</com.android.internal.widget.ConversationHeaderLinearLayout>
+</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml
index 6f3c15adb082..f1729b3c2f76 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_call.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -47,7 +47,7 @@
>
<include
- layout="@layout/notification_2025_conversation_header"
+ layout="@layout/notification_template_conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
new file mode 100644
index 000000000000..f80411103501
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.ConversationLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="conversation"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+
+ <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_2025_min_height"
+ android:clipChildren="false"
+ >
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_2025_left_icon_size"
+ android:layout_height="@dimen/notification_2025_left_icon_size"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ android:visibility="gone"
+ />
+
+ <include layout="@layout/notification_2025_conversation_icon_container" />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_2025_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+
+ <!--
+ NOTE: to make the expansion animation of id/notification_messaging happen vertically,
+ its X positioning must be the left edge of the notification, so instead of putting the
+ layout_marginStart on the id/notification_headerless_view_row, we put it on
+ id/notification_top_line, making the layout here just a bit different from the base.
+ -->
+ <LinearLayout
+ android:id="@+id/notification_headerless_view_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clipChildren="false"
+ >
+
+ <!--
+ NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
+ have the id/notification_headerless_view_column, as that is used for modifying
+ vertical margins to accommodate the single-line state that base supports
+ -->
+ <LinearLayout
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_2025_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_2025_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ >
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/notification_messaging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:spacing="@dimen/notification_messaging_spacing" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Images -->
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/conversation_image_message_container"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:forceHasOverlappingRendering="false"
+ android:spacing="0dp"
+ android:clipChildren="false"
+ android:visibility="gone"
+ />
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_2025_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
+ </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-20dp"
+ android:clipChildren="false"
+ android:orientation="vertical">
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end" />
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
+</LinearLayout>
+</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml
deleted file mode 100644
index 24b6ad09d3f7..000000000000
--- a/core/res/res/layout/notification_2025_template_conversation.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- extends FrameLayout -->
-<com.android.internal.widget.ConversationLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:tag="conversation"
- android:theme="@style/Theme.DeviceDefault.Notification"
- >
-
- <include layout="@layout/notification_2025_conversation_icon_container" />
-
- <!-- Wraps entire "expandable" notification -->
- <com.android.internal.widget.RemeasuringLinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="top"
- android:clipToPadding="false"
- android:clipChildren="false"
- android:orientation="vertical"
- >
- <!-- LinearLayout for Expand Button-->
- <com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/expand_button_and_content_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="start|top"
- android:orientation="horizontal"
- android:clipChildren="false"
- android:clipToPadding="false">
- <!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc-->
- <com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
- android:layout_width="0dp"
- android:orientation="vertical"
- android:layout_height="wrap_content"
- android:layout_weight="1">
-
- <!-- Header -->
-
- <!-- Use layout_marginStart instead of paddingStart to work around strange
- measurement behavior on lower display densities. -->
- <include
- layout="@layout/notification_2025_conversation_header"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="2dp"
- android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- />
-
- <!-- Messages -->
- <com.android.internal.widget.MessagingLinearLayout
- android:id="@+id/notification_messaging"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/notification_text_size"
- android:spacing="@dimen/notification_messaging_spacing"
- android:clipToPadding="false"
- android:clipChildren="false"
- />
- </com.android.internal.widget.RemeasuringLinearLayout>
-
- <!-- This is where the expand button container will be placed when collapsed-->
- </com.android.internal.widget.RemeasuringLinearLayout>
-
- <include layout="@layout/notification_template_smart_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin"
- android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end" />
- <include layout="@layout/notification_material_action_list" />
- </com.android.internal.widget.RemeasuringLinearLayout>
-
- <!--expand_button_a11y_container ensures talkback focus order is correct when view is expanded.
- The -1px of marginTop and 1px of paddingTop make sure expand_button_a11y_container is prior to
- its sibling view in accessibility focus order.
- {see android.view.ViewGroup.addChildrenForAccessibility()}
- expand_button_container will be moved under expand_button_and_content_container when collapsed,
- this dynamic movement ensures message can flow under expand button when expanded-->
- <FrameLayout
- android:id="@+id/expand_button_a11y_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="end|top"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:layout_marginTop="-1px"
- android:paddingTop="1px"
- >
- <!--expand_button_container is dynamically placed between here and at the end of the
- layout. It starts here since only FrameLayout layout params have gravity-->
- <LinearLayout
- android:id="@+id/expand_button_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="end|top"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:orientation="vertical">
- <include layout="@layout/notification_close_button"
- android:layout_width="@dimen/notification_close_button_size"
- android:layout_height="@dimen/notification_close_button_size"
- android:layout_gravity="end"
- android:layout_marginEnd="20dp"
- />
- <!--expand_button_touch_container makes sure that we can nicely center the expand
- content in the collapsed layout while the parent makes sure that we're never laid out
- bigger than the messaging content.-->
- <LinearLayout
- android:id="@+id/expand_button_touch_container"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/conversation_expand_button_height"
- android:orientation="horizontal"
- android:layout_gravity="end|top"
- android:paddingEnd="0dp"
- android:clipToPadding="false"
- android:clipChildren="false"
- >
- <!-- Images -->
- <com.android.internal.widget.MessagingLinearLayout
- android:id="@+id/conversation_image_message_container"
- android:forceHasOverlappingRendering="false"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:layout_marginStart="@dimen/conversation_image_start_margin"
- android:spacing="0dp"
- android:layout_gravity="center"
- android:clipToPadding="false"
- android:clipChildren="false"
- />
- <include layout="@layout/notification_2025_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="top|end"
- />
- </LinearLayout>
- </LinearLayout>
- </FrameLayout>
-</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml
index 0be61253c917..2114831f4c15 100644
--- a/core/res/res/layout/notification_2025_template_expanded_call.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -55,7 +55,7 @@
>
<include
- layout="@layout/notification_2025_conversation_header"
+ layout="@layout/notification_template_conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
new file mode 100644
index 000000000000..d7e8bb3b6da2
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.ConversationLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:tag="conversation"
+ >
+
+ <include layout="@layout/notification_2025_conversation_header"/>
+
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:clipChildren="false"
+ android:orientation="vertical">
+
+ <!-- Note: the top margin is being set in code based on the estimated space needed for
+ the header text. -->
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_weight="1"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ >
+
+ <include layout="@layout/notification_template_part_line1"/>
+
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/notification_messaging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:spacing="@dimen/notification_messaging_spacing" />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end" />
+
+ <include layout="@layout/notification_material_action_list" />
+
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <include layout="@layout/notification_template_right_icon" />
+
+</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
index 177706c6d58d..20abfee6a4b6 100644
--- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -36,17 +36,21 @@
android:clipChildren="false"
android:orientation="vertical">
+ <!-- Note: the top margin is being set in code based on the estimated space needed for
+ the header text. -->
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
- android:layout_marginTop="@dimen/notification_2025_header_height"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:orientation="vertical"
android:clipChildren="false"
>
+
+ <include layout="@layout/notification_template_part_line1"/>
+
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_2025_top_line_views.xml b/core/res/res/layout/notification_2025_top_line_views.xml
index 74873463391e..a9bde9d48dcf 100644
--- a/core/res/res/layout/notification_2025_top_line_views.xml
+++ b/core/res/res/layout/notification_2025_top_line_views.xml
@@ -20,7 +20,7 @@
<merge
xmlns:android="http://schemas.android.com/apk/res/android">
- <TextView
+ <com.android.internal.widget.ObservableTextView
android:id="@+id/app_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index f2ec56c69374..59ed25a1865f 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -64,6 +64,13 @@
<integer name="auto_data_switch_performance_stability_time_threshold_millis">120000</integer>
<java-symbol type="integer" name="auto_data_switch_performance_stability_time_threshold_millis" />
+ <!-- Define the bar for switching data back to the default SIM when both SIMs are out of service
+ in milliseconds. A value of 0 means an immediate switch, otherwise for a negative value,
+ the threshold defined by auto_data_switch_availability_stability_time_threshold_millis
+ will be used instead. -->
+ <integer name="auto_data_switch_availability_switchback_stability_time_threshold_millis">150000</integer>
+ <java-symbol type="integer" name="auto_data_switch_availability_switchback_stability_time_threshold_millis" />
+
<!-- Define the maximum retry times when a validation for switching failed.-->
<integer name="auto_data_switch_validation_max_retry">7</integer>
<java-symbol type="integer" name="auto_data_switch_validation_max_retry" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 26f0ab3f28e1..b013ffd41ecb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3264,6 +3264,7 @@
<java-symbol type="dimen" name="notification_content_margin" />
<java-symbol type="dimen" name="notification_2025_margin" />
<java-symbol type="dimen" name="notification_2025_content_margin_top" />
+ <java-symbol type="dimen" name="notification_2025_content_margin_start" />
<java-symbol type="dimen" name="notification_2025_expand_button_horizontal_icon_padding" />
<java-symbol type="dimen" name="notification_2025_expand_button_reduced_end_padding" />
<java-symbol type="dimen" name="notification_progress_margin_horizontal" />
@@ -4705,7 +4706,8 @@
<java-symbol type="dimen" name="conversation_icon_container_top_padding" />
<java-symbol type="dimen" name="conversation_icon_container_top_padding_small_avatar" />
<java-symbol type="layout" name="notification_template_material_conversation" />
- <java-symbol type="layout" name="notification_2025_template_conversation" />
+ <java-symbol type="layout" name="notification_2025_template_collapsed_conversation" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_conversation" />
<java-symbol type="dimen" name="button_padding_horizontal_material" />
<java-symbol type="dimen" name="button_inset_horizontal_material" />
<java-symbol type="layout" name="conversation_face_pile_layout" />
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 9383807ec761..8ef105f79988 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -56,6 +56,7 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -116,7 +117,8 @@ public class DisplayManagerGlobalTest {
@Test
public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
- ALL_DISPLAY_EVENTS, /* packageName= */ null);
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ true);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -151,7 +153,7 @@ public class DisplayManagerGlobalTest {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| INTERNAL_EVENT_FLAG_DISPLAY_STATE,
- null);
+ null, /* isEventFilterExplicit */ true);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -172,11 +174,80 @@ public class DisplayManagerGlobalTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+ public void test_refreshRateRegistration_implicitRRCallbacksEnabled()
+ throws RemoteException {
+ // Subscription without supplied events doesn't subscribe to RR events
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ // After registering to refresh rate changes, subscription without supplied events subscribe
+ // to RR events
+ mDisplayManagerGlobal.registerForRefreshRateChanges();
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+
+ // Assert all the existing listeners are also subscribed to RR events
+ CopyOnWriteArrayList<DisplayManagerGlobal.DisplayListenerDelegate> delegates =
+ mDisplayManagerGlobal.getDisplayListeners();
+ for (DisplayManagerGlobal.DisplayListenerDelegate delegate: delegates) {
+ assertEquals(ALL_DISPLAY_EVENTS | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+ delegate.mInternalEventFlagsMask);
+ }
+
+ // Subscription to RR when events are supplied doesn't happen
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ true);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ // Assert one listeners are not subscribed to RR events
+ delegates = mDisplayManagerGlobal.getDisplayListeners();
+ int subscribedListenersCount = 0;
+ int nonSubscribedListenersCount = 0;
+ for (DisplayManagerGlobal.DisplayListenerDelegate delegate: delegates) {
+
+ if (delegate.isEventFilterExplicit()) {
+ assertEquals(ALL_DISPLAY_EVENTS, delegate.mInternalEventFlagsMask);
+ nonSubscribedListenersCount++;
+ } else {
+ assertEquals(ALL_DISPLAY_EVENTS | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+ delegate.mInternalEventFlagsMask);
+ subscribedListenersCount++;
+ }
+ }
+
+ assertEquals(2, subscribedListenersCount);
+ assertEquals(1, nonSubscribedListenersCount);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+ public void test_refreshRateRegistration_implicitRRCallbacksDisabled()
+ throws RemoteException {
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+ }
+
+ @Test
public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
// First we subscribe to all events in order to test that the subsequent calls to
// registerDisplayListener will update the event mask.
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
- ALL_DISPLAY_EVENTS, /* packageName= */ null);
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ true);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -184,21 +255,24 @@ public class DisplayManagerGlobalTest {
int displayId = 1;
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
- & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null);
+ & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null,
+ /* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
Mockito.verifyZeroInteractions(mDisplayListener);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
- & ~DISPLAY_CHANGE_EVENTS, null);
+ & ~DISPLAY_CHANGE_EVENTS, null,
+ /* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
waitForHandler();
Mockito.verifyZeroInteractions(mDisplayListener);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
- & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null);
+ & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null,
+ /* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
Mockito.verifyZeroInteractions(mDisplayListener);
@@ -218,11 +292,34 @@ public class DisplayManagerGlobalTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+ public void test_registerNativeRefreshRateCallbacks_enablesRRImplicitRegistrations()
+ throws RemoteException {
+ // Registering the display listener without supplied events doesn't subscribe to RR events
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ // Native subscription for refresh rates is done
+ mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+
+ // Registering the display listener without supplied events subscribe to RR events
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null,
+ /* isEventFilterExplicit */ false);
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+ }
+
+ @Test
public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
throws RemoteException {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
- null);
+ null, /* isEventFilterExplicit */ true);
InOrder inOrder = Mockito.inOrder(mDisplayManager);
inOrder.verify(mDisplayManager)
@@ -260,9 +357,10 @@ public class DisplayManagerGlobalTest {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
- null /* packageName */);
+ null /* packageName */, /* isEventFilterExplicit */ true);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler,
- DISPLAY_CHANGE_EVENTS, null /* packageName */);
+ DISPLAY_CHANGE_EVENTS, null /* packageName */,
+ /* isEventFilterExplicit */ true);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
waitForHandler();
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index e6361e10cfa7..6adceb96d977 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -230,7 +230,9 @@ public class InsetsSourceConsumerTest {
new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
false /* initialVisible */, new Point(), Insets.NONE),
new int[1], hideTypes, new int[1], new int[1]);
- assertTrue(mRemoveSurfaceCalled);
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ assertTrue(mRemoveSurfaceCalled);
+ }
assertEquals(0, hideTypes[0]);
});
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f5f3f0fe52eb..a0c68ad44379 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -670,7 +670,4 @@
<dimen name="desktop_windowing_education_promo_height">352dp</dimen>
<!-- The corner radius of the desktop windowing education promo. -->
<dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen>
-
- <!-- The corner radius of freeform tasks in desktop windowing. -->
- <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 11a6f32d7454..23c9caf2046c 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -46,4 +46,7 @@
<dimen name="drop_target_expanded_view_height">578</dimen>
<dimen name="drop_target_expanded_view_padding_bottom">108</dimen>
<dimen name="drop_target_expanded_view_padding_horizontal">24</dimen>
+
+ <!-- The corner radius of freeform tasks in desktop windowing. -->
+ <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
</resources> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index 14338a49ee2f..0e4a6b9fb083 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -16,12 +16,14 @@
package com.android.wm.shell.shared.desktopmode
+import android.Manifest.permission.SYSTEM_ALERT_WINDOW
import android.app.TaskInfo
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED
import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION
import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
+import android.content.pm.PackageManager
import android.window.DesktopModeFlags
import com.android.internal.R
@@ -32,8 +34,10 @@ import com.android.internal.R
class DesktopModeCompatPolicy(private val context: Context) {
private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi)
+ private val pkgManager: PackageManager
+ get() = context.getPackageManager()
private val defaultHomePackage: String?
- get() = context.getPackageManager().getHomeActivities(ArrayList())?.packageName
+ get() = pkgManager.getHomeActivities(ArrayList())?.packageName
/**
* If the top activity should be exempt from desktop windowing and forced back to fullscreen.
@@ -47,11 +51,12 @@ class DesktopModeCompatPolicy(private val context: Context) {
fun isTopActivityExemptFromDesktopWindowing(packageName: String?,
numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
- DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
- && ((isSystemUiTask(packageName)
- || isPartOfDefaultHomePackageOrNoHomeAvailable(packageName)
- || isTransparentTask(isActivityStackTransparent, numActivities))
- && !isTopActivityNoDisplay)
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue &&
+ ((isSystemUiTask(packageName) ||
+ isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) ||
+ (isTransparentTask(isActivityStackTransparent, numActivities) &&
+ hasFullscreenTransparentPermission(packageName))) &&
+ !isTopActivityNoDisplay)
/**
* Whether the caption insets should be excluded from configuration for system to handle.
@@ -83,6 +88,26 @@ class DesktopModeCompatPolicy(private val context: Context) {
private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
+ // Checks if the app for the given package has the SYSTEM_ALERT_WINDOW permission.
+ private fun hasFullscreenTransparentPermission(packageName: String?): Boolean {
+ if (DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue) {
+ if (packageName == null) {
+ return false
+ }
+ return try {
+ val packageInfo = pkgManager.getPackageInfo(
+ packageName,
+ PackageManager.GET_PERMISSIONS
+ )
+ packageInfo?.requestedPermissions?.contains(SYSTEM_ALERT_WINDOW) == true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false // Package not found
+ }
+ }
+ // If the flag is disabled we make this condition neutral.
+ return true
+ }
+
/**
* Returns true if the tasks base activity is part of the default home package, or there is
* currently no default home package available.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 8377a35a9e7d..87a4115ccd3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -324,8 +324,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
applyVisibilityToLeash(imeSourceControl);
}
- if (!mImeShowing) {
- removeImeSurface(mDisplayId);
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (!mImeShowing) {
+ removeImeSurface(mDisplayId);
+ }
}
}
} else {
@@ -663,7 +665,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.hide(animatingLeash);
- removeImeSurface(mDisplayId);
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ removeImeSurface(mDisplayId);
+ }
if (android.view.inputmethod.Flags.refactorInsetsController()) {
setVisibleDirectly(false /* visible */, statsToken);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt
new file mode 100644
index 000000000000..7a5bc1383ccf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.RectF
+import android.view.SurfaceControl
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.shared.annotations.ShellDesktopThread
+
+/**
+ * Controller to manage the indicators that show users the current position of the dragged window on
+ * the new display when performing drag move across displays.
+ */
+class MultiDisplayDragMoveIndicatorController(
+ private val displayController: DisplayController,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val indicatorSurfaceFactory: MultiDisplayDragMoveIndicatorSurface.Factory,
+ @ShellDesktopThread private val desktopExecutor: ShellExecutor,
+) {
+ @ShellDesktopThread
+ private val dragIndicators =
+ mutableMapOf<Int, MutableMap<Int, MultiDisplayDragMoveIndicatorSurface>>()
+
+ /**
+ * Called during drag move, which started at [startDisplayId]. Updates the position and
+ * visibility of the drag move indicators for the [taskInfo] based on [boundsDp] on the
+ * destination displays ([displayIds]) as the dragged window moves. [transactionSupplier]
+ * provides a [SurfaceControl.Transaction] for applying changes to the indicator surfaces.
+ *
+ * It is executed on the [desktopExecutor] to prevent blocking the main thread and avoid jank,
+ * as creating and manipulating surfaces can be expensive.
+ */
+ fun onDragMove(
+ boundsDp: RectF,
+ startDisplayId: Int,
+ taskInfo: RunningTaskInfo,
+ displayIds: Set<Int>,
+ transactionSupplier: () -> SurfaceControl.Transaction,
+ ) {
+ desktopExecutor.execute {
+ for (displayId in displayIds) {
+ if (displayId == startDisplayId) {
+ // No need to render indicators on the original display where the drag started.
+ continue
+ }
+ val displayLayout = displayController.getDisplayLayout(displayId) ?: continue
+ val shouldBeVisible =
+ RectF.intersects(RectF(boundsDp), displayLayout.globalBoundsDp())
+ if (
+ dragIndicators[taskInfo.taskId]?.containsKey(displayId) != true &&
+ !shouldBeVisible
+ ) {
+ // Skip this display if:
+ // - It doesn't have an existing indicator that needs to be updated, AND
+ // - The latest dragged window bounds don't intersect with this display.
+ continue
+ }
+
+ val boundsPx =
+ MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
+ boundsDp,
+ displayLayout,
+ )
+
+ // Get or create the inner map for the current task.
+ val dragIndicatorsForTask =
+ dragIndicators.getOrPut(taskInfo.taskId) { mutableMapOf() }
+ dragIndicatorsForTask[displayId]?.also { existingIndicator ->
+ val transaction = transactionSupplier()
+ existingIndicator.relayout(boundsPx, transaction, shouldBeVisible)
+ transaction.apply()
+ } ?: run {
+ val newIndicator =
+ indicatorSurfaceFactory.create(
+ taskInfo,
+ displayController.getDisplay(displayId),
+ )
+ newIndicator.show(
+ transactionSupplier(),
+ taskInfo,
+ rootTaskDisplayAreaOrganizer,
+ displayId,
+ boundsPx,
+ )
+ dragIndicatorsForTask[displayId] = newIndicator
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when the drag ends. Disposes of the drag move indicator surfaces associated with the
+ * given [taskId]. [transactionSupplier] provides a [SurfaceControl.Transaction] for applying
+ * changes to the indicator surfaces.
+ *
+ * It is executed on the [desktopExecutor] to ensure that any pending `onDragMove` operations
+ * have completed before disposing of the surfaces.
+ */
+ fun onDragEnd(taskId: Int, transactionSupplier: () -> SurfaceControl.Transaction) {
+ desktopExecutor.execute {
+ dragIndicators.remove(taskId)?.values?.takeIf { it.isNotEmpty() }?.let { indicators ->
+ val transaction = transactionSupplier()
+ indicators.forEach { indicator ->
+ indicator.disposeSurface(transaction)
+ }
+ transaction.apply()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt
new file mode 100644
index 000000000000..d05d3b0903d7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.Trace
+import android.view.Display
+import android.view.SurfaceControl
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.ui.graphics.toArgb
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.common.Theme
+
+/**
+ * Represents the indicator surface that visualizes the current position of a dragged window during
+ * a multi-display drag operation.
+ *
+ * This class manages the creation, display, and manipulation of the [SurfaceControl]s that act as a
+ * visual indicator, providing feedback to the user about the dragged window's location.
+ */
+class MultiDisplayDragMoveIndicatorSurface(
+ context: Context,
+ taskInfo: RunningTaskInfo,
+ display: Display,
+ surfaceControlBuilderFactory: Factory.SurfaceControlBuilderFactory,
+) {
+ private var isVisible = false
+
+ // A container surface to host the veil background
+ private var veilSurface: SurfaceControl? = null
+
+ private val decorThemeUtil = DecorThemeUtil(context)
+ private val lightColors = dynamicLightColorScheme(context)
+ private val darkColors = dynamicDarkColorScheme(context)
+
+ init {
+ Trace.beginSection("DragIndicatorSurface#init")
+
+ val displayId = display.displayId
+ veilSurface =
+ surfaceControlBuilderFactory
+ .create("Drag indicator veil of Task=${taskInfo.taskId} Display=$displayId")
+ .setColorLayer()
+ .setCallsite("DragIndicatorSurface#init")
+ .setHidden(true)
+ .build()
+
+ // TODO: b/383069173 - Add icon for the surface.
+
+ Trace.endSection()
+ }
+
+ /**
+ * Disposes the indicator surface using the provided [transaction].
+ */
+ fun disposeSurface(transaction: SurfaceControl.Transaction) {
+ veilSurface?.let { veil -> transaction.remove(veil) }
+ veilSurface = null
+ }
+
+ /**
+ * Shows the indicator surface at [bounds] on the specified display ([displayId]),
+ * visualizing the drag of the [taskInfo]. The indicator surface is shown using [transaction],
+ * and the [rootTaskDisplayAreaOrganizer] is used to reparent the surfaces.
+ */
+ fun show(
+ transaction: SurfaceControl.Transaction,
+ taskInfo: RunningTaskInfo,
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ displayId: Int,
+ bounds: Rect,
+ ) {
+ val backgroundColor =
+ when (decorThemeUtil.getAppTheme(taskInfo)) {
+ Theme.LIGHT -> lightColors.surfaceContainer
+ Theme.DARK -> darkColors.surfaceContainer
+ }
+ val veil = veilSurface ?: return
+ isVisible = true
+
+ rootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, veil, transaction)
+ relayout(bounds, transaction, shouldBeVisible = true)
+ transaction.show(veil).setColor(veil, Color.valueOf(backgroundColor.toArgb()).components)
+ transaction.apply()
+ }
+
+ /**
+ * Repositions and resizes the indicator surface based on [bounds] using [transaction]. The
+ * [shouldBeVisible] flag indicates whether the indicator is within the display after relayout.
+ */
+ fun relayout(bounds: Rect, transaction: SurfaceControl.Transaction, shouldBeVisible: Boolean) {
+ if (!isVisible && !shouldBeVisible) {
+ // No need to relayout if the surface is already invisible and should not be visible.
+ return
+ }
+ isVisible = shouldBeVisible
+ val veil = veilSurface ?: return
+ transaction.setCrop(veil, bounds)
+ }
+
+ /**
+ * Factory for creating [MultiDisplayDragMoveIndicatorSurface] instances with the [context].
+ */
+ class Factory(private val context: Context) {
+ private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory =
+ object : SurfaceControlBuilderFactory {}
+
+ /**
+ * Creates a new [MultiDisplayDragMoveIndicatorSurface] instance to visualize the drag
+ * operation of the [taskInfo] on the given [display].
+ */
+ fun create(
+ taskInfo: RunningTaskInfo,
+ display: Display,
+ ) = MultiDisplayDragMoveIndicatorSurface(
+ context,
+ taskInfo,
+ display,
+ surfaceControlBuilderFactory,
+ )
+
+ /**
+ * Interface for creating [SurfaceControl.Builder] instances.
+ *
+ * This provides an abstraction over [SurfaceControl.Builder] creation for testing purposes.
+ */
+ interface SurfaceControlBuilderFactory {
+ fun create(name: String): SurfaceControl.Builder {
+ return SurfaceControl.Builder().setName(name)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2fd8c27d5970..5a246e7c99b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -68,6 +68,8 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorSurface;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -82,6 +84,7 @@ import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
+import com.android.wm.shell.desktopmode.DesktopDisplayModeController;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopMinimizationTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
@@ -989,7 +992,8 @@ public abstract class WMShellModule {
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DesktopTilingDecorViewModel desktopTilingDecorViewModel
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -1006,7 +1010,30 @@ public abstract class WMShellModule {
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy,
- desktopTilingDecorViewModel));
+ desktopTilingDecorViewModel,
+ multiDisplayDragMoveIndicatorController));
+ }
+
+ @WMSingleton
+ @Provides
+ static MultiDisplayDragMoveIndicatorController
+ providesMultiDisplayDragMoveIndicatorController(
+ DisplayController displayController,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ MultiDisplayDragMoveIndicatorSurface.Factory
+ multiDisplayDragMoveIndicatorSurfaceFactory,
+ @ShellDesktopThread ShellExecutor desktopExecutor
+ ) {
+ return new MultiDisplayDragMoveIndicatorController(
+ displayController, rootTaskDisplayAreaOrganizer,
+ multiDisplayDragMoveIndicatorSurfaceFactory, desktopExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static MultiDisplayDragMoveIndicatorSurface.Factory
+ providesMultiDisplayDragMoveIndicatorSurfaceFactory(Context context) {
+ return new MultiDisplayDragMoveIndicatorSurface.Factory(context);
}
@WMSingleton
@@ -1230,13 +1257,10 @@ public abstract class WMShellModule {
static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler(
Context context,
ShellInit shellInit,
- Transitions transitions,
DisplayController displayController,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- IWindowManager windowManager,
Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
- ShellTaskOrganizer shellTaskOrganizer
+ Optional<DesktopDisplayModeController> desktopDisplayModeController
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.empty();
@@ -1245,13 +1269,10 @@ public abstract class WMShellModule {
new DesktopDisplayEventHandler(
context,
shellInit,
- transitions,
displayController,
- rootTaskDisplayAreaOrganizer,
- windowManager,
desktopUserRepositories.get(),
desktopTasksController.get(),
- shellTaskOrganizer));
+ desktopDisplayModeController.get()));
}
@WMSingleton
@@ -1381,6 +1402,29 @@ public abstract class WMShellModule {
return new DesktopModeUiEventLogger(uiEventLogger, packageManager);
}
+ @WMSingleton
+ @Provides
+ static Optional<DesktopDisplayModeController> provideDesktopDisplayModeController(
+ Context context,
+ Transitions transitions,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ IWindowManager windowManager,
+ ShellTaskOrganizer shellTaskOrganizer,
+ DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider
+ ) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ new DesktopDisplayModeController(
+ context,
+ transitions,
+ rootTaskDisplayAreaOrganizer,
+ windowManager,
+ shellTaskOrganizer,
+ desktopWallpaperActivityTokenProvider));
+ }
+
//
// App zoom out
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index c38558d7bde9..946e7952dd50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -16,41 +16,25 @@
package com.android.wm.shell.desktopmode
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
-import android.app.WindowConfiguration.windowingModeToString
import android.content.Context
-import android.provider.Settings
-import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
import android.view.Display.DEFAULT_DISPLAY
-import android.view.IWindowManager
-import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DesktopExperienceFlags
-import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.transition.Transitions
/** Handles display events in desktop mode */
class DesktopDisplayEventHandler(
private val context: Context,
shellInit: ShellInit,
- private val transitions: Transitions,
private val displayController: DisplayController,
- private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- private val windowManager: IWindowManager,
private val desktopUserRepositories: DesktopUserRepositories,
private val desktopTasksController: DesktopTasksController,
- private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val desktopDisplayModeController: DesktopDisplayModeController,
) : OnDisplaysChangedListener, OnDeskRemovedListener {
private val desktopRepository: DesktopRepository
@@ -70,7 +54,7 @@ class DesktopDisplayEventHandler(
override fun onDisplayAdded(displayId: Int) {
if (displayId != DEFAULT_DISPLAY) {
- refreshDisplayWindowingMode()
+ desktopDisplayModeController.refreshDisplayWindowingMode()
}
if (!supportsDesks(displayId)) {
@@ -88,7 +72,7 @@ class DesktopDisplayEventHandler(
override fun onDisplayRemoved(displayId: Int) {
if (displayId != DEFAULT_DISPLAY) {
- refreshDisplayWindowingMode()
+ desktopDisplayModeController.refreshDisplayWindowingMode()
}
// TODO: b/362720497 - move desks in closing display to the remaining desk.
@@ -102,65 +86,6 @@ class DesktopDisplayEventHandler(
}
}
- private fun refreshDisplayWindowingMode() {
- if (!Flags.enableDisplayWindowingModeSwitching()) return
- // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
- val isExtendedDisplayEnabled =
- 0 !=
- Settings.Global.getInt(
- context.contentResolver,
- DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
- 0,
- )
- if (!isExtendedDisplayEnabled) {
- // No action needed in mirror or projected mode.
- return
- }
-
- val hasNonDefaultDisplay =
- rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
- displayId != DEFAULT_DISPLAY
- }
- val targetDisplayWindowingMode =
- if (hasNonDefaultDisplay) {
- WINDOWING_MODE_FREEFORM
- } else {
- // Use the default display windowing mode when no non-default display.
- windowManager.getWindowingMode(DEFAULT_DISPLAY)
- }
- val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
- requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
- val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
- if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
- // Already in the target mode.
- return
- }
-
- logV(
- "As an external display is connected, changing default display's windowing mode from" +
- " ${windowingModeToString(currentDisplayWindowingMode)}" +
- " to ${windowingModeToString(targetDisplayWindowingMode)}"
- )
-
- val wct = WindowContainerTransaction()
- wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
- shellTaskOrganizer
- .getRunningTasks(DEFAULT_DISPLAY)
- .filter { it.activityType == ACTIVITY_TYPE_STANDARD }
- .forEach {
- // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
- when (it.windowingMode) {
- currentDisplayWindowingMode -> {
- wct.setWindowingMode(it.token, currentDisplayWindowingMode)
- }
- targetDisplayWindowingMode -> {
- wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
- }
- }
- }
- transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
- }
-
// TODO: b/362720497 - connected/projected display considerations.
private fun supportsDesks(displayId: Int): Boolean =
DesktopModeStatus.canEnterDesktopMode(context)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
new file mode 100644
index 000000000000..c9a63ff818f5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.windowingModeToString
+import android.content.Context
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.transition.Transitions
+
+/** Controls the display windowing mode in desktop mode */
+class DesktopDisplayModeController(
+ private val context: Context,
+ private val transitions: Transitions,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val windowManager: IWindowManager,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+) {
+
+ fun refreshDisplayWindowingMode() {
+ if (!Flags.enableDisplayWindowingModeSwitching()) return
+ // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
+ val isExtendedDisplayEnabled =
+ 0 !=
+ Settings.Global.getInt(
+ context.contentResolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+ 0,
+ )
+ if (!isExtendedDisplayEnabled) {
+ // No action needed in mirror or projected mode.
+ return
+ }
+
+ val hasNonDefaultDisplay =
+ rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
+ displayId != DEFAULT_DISPLAY
+ }
+ val targetDisplayWindowingMode =
+ if (hasNonDefaultDisplay) {
+ WINDOWING_MODE_FREEFORM
+ } else {
+ // Use the default display windowing mode when no non-default display.
+ windowManager.getWindowingMode(DEFAULT_DISPLAY)
+ }
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
+ requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
+ val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
+ // Already in the target mode.
+ return
+ }
+
+ logV(
+ "As an external display is connected, changing default display's windowing mode from" +
+ " ${windowingModeToString(currentDisplayWindowingMode)}" +
+ " to ${windowingModeToString(targetDisplayWindowingMode)}"
+ )
+
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
+ shellTaskOrganizer
+ .getRunningTasks(DEFAULT_DISPLAY)
+ .filter { it.activityType == ACTIVITY_TYPE_STANDARD }
+ .forEach {
+ // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
+ when (it.windowingMode) {
+ currentDisplayWindowingMode -> {
+ wct.setWindowingMode(it.token, currentDisplayWindowingMode)
+ }
+ targetDisplayWindowingMode -> {
+ wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
+ }
+ }
+ }
+ // The override windowing mode of DesktopWallpaper can be UNDEFINED on fullscreen-display
+ // right after the first launch while its resolved windowing mode is FULLSCREEN. We here
+ // it has the FULLSCREEN override windowing mode.
+ desktopWallpaperActivityTokenProvider.getToken(DEFAULT_DISPLAY)?.let { token ->
+ wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+ }
+ transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ }
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopDisplayModeController"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 031925b2997a..c60fc1646c32 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
@@ -686,6 +686,11 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) {
+ logD(
+ "Top transparent fullscreen task set for display: taskId=%d, displayId=%d",
+ taskId,
+ displayId,
+ )
desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId
}
@@ -703,6 +708,11 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun clearTopTransparentFullscreenTaskId(displayId: Int) {
+ logD(
+ "Top transparent fullscreen task cleared for display: taskId=%d, displayId=%d",
+ desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId,
+ displayId,
+ )
desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null
}
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 93058db0c171..0fbb84075179 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -117,6 +117,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
+import com.android.wm.shell.shared.R as SharedR
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellDesktopThread
@@ -2015,7 +2016,9 @@ class DesktopTasksController(
}
val cornerRadius =
context.resources
- .getDimensionPixelSize(R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+ .getDimensionPixelSize(
+ SharedR.dimen.desktop_windowing_freeform_rounded_corner_radius
+ )
.toFloat()
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
index 59add47fc79d..5f45192569e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
@@ -18,13 +18,11 @@ package com.android.wm.shell.desktopmode;
/**
* Defines the state of desks on a display whose ID is `displayId`, which is:
- * - `canCreateDesks`: whether it's possible to create new desks on this display.
* - `activeDeskId`: the currently active desk Id, or `-1` if none is active.
* - `deskId`: the list of desk Ids of the available desks on this display.
*/
parcelable DisplayDeskState {
int displayId;
- boolean canCreateDesk;
int activeDeskId;
int[] deskIds;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 7ed1581cdfdb..cefbd8947feb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -28,7 +28,7 @@ oneway interface IDesktopTaskListener {
* Called once when the listener first gets connected to initialize it with the current state of
* desks in Shell.
*/
- void onListenerConnected(in DisplayDeskState[] displayDeskStates);
+ void onListenerConnected(in DisplayDeskState[] displayDeskStates, boolean canCreateDesks);
/** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
@@ -49,10 +49,10 @@ oneway interface IDesktopTaskListener {
void onExitDesktopModeTransitionStarted(int transitionDuration);
/**
- * Called when the conditions that allow the creation of a new desk on the display whose ID is
- * `displayId` changes to `canCreateDesks`. It's also called when a new display is added.
+ * Called when the conditions that allow the creation of a new desk changes. This is a global
+ * state for the entire device.
*/
- void onCanCreateDesksChanged(int displayId, boolean canCreateDesks);
+ void onCanCreateDesksChanged(boolean canCreateDesks);
/** Called when a desk whose ID is `deskId` is added to the display whose ID is `displayId`. */
void onDeskAdded(int displayId, int deskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 847a0383e7d0..d6182e9a78dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -75,11 +75,11 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.Flags;
-import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.R;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.HomeTransitionObserver;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 5a6ea214e561..cf139a008164 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -103,6 +103,7 @@ import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -258,6 +259,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final RecentsTransitionHandler mRecentsTransitionHandler;
private final DesktopModeCompatPolicy mDesktopModeCompatPolicy;
private final DesktopTilingDecorViewModel mDesktopTilingDecorViewModel;
+ private final MultiDisplayDragMoveIndicatorController mMultiDisplayDragMoveIndicatorController;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -296,7 +298,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
this(
context,
shellExecutor,
@@ -340,7 +343,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
taskResourceLoader,
recentsTransitionHandler,
desktopModeCompatPolicy,
- desktopTilingDecorViewModel);
+ desktopTilingDecorViewModel,
+ multiDisplayDragMoveIndicatorController);
}
@VisibleForTesting
@@ -387,7 +391,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
WindowDecorTaskResourceLoader taskResourceLoader,
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
+ DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -460,6 +465,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDesktopModeCompatPolicy = desktopModeCompatPolicy;
mDesktopTilingDecorViewModel = desktopTilingDecorViewModel;
mDesktopTasksController.setSnapEventHandler(this);
+ mMultiDisplayDragMoveIndicatorController = multiDisplayDragMoveIndicatorController;
shellInit.addInitCallback(this::onInit, this);
}
@@ -1759,7 +1765,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mTransitions,
mInteractionJankMonitor,
mTransactionFactory,
- mMainHandler);
+ mMainHandler,
+ mMultiDisplayDragMoveIndicatorController);
windowDecoration.setTaskDragResizer(taskPositioner);
final DesktopModeTouchEventListener touchEventListener =
@@ -2056,7 +2063,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
Transitions transitions,
InteractionJankMonitor interactionJankMonitor,
Supplier<SurfaceControl.Transaction> transactionFactory,
- Handler handler) {
+ Handler handler,
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
// TODO(b/383632995): Update when the flag is launched.
? (Flags.enableConnectedDisplaysWindowDrag()
@@ -2067,7 +2075,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
dragEventListener,
transitions,
interactionJankMonitor,
- handler)
+ handler,
+ multiDisplayDragMoveIndicatorController)
: new VeiledResizeTaskPositioner(
taskOrganizer,
windowDecoration,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index dca376f7df0e..6165dbf686fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -1069,7 +1069,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private static int getCornerRadius(@NonNull Context context, int layoutResId) {
if (layoutResId == R.layout.desktop_mode_app_header) {
return loadDimensionPixelSize(context.getResources(),
- R.dimen.desktop_windowing_freeform_rounded_corner_radius);
+ com.android.wm.shell.shared.R.dimen
+ .desktop_windowing_freeform_rounded_corner_radius);
}
return INVALID_CORNER_RADIUS;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index bb20292a51d4..c6cb62d153ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor
import android.graphics.PointF
import android.graphics.Rect
+import android.hardware.display.DisplayTopology
import android.os.Handler
import android.os.IBinder
import android.os.Looper
@@ -32,10 +33,10 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import java.util.concurrent.TimeUnit
-import java.util.function.Supplier
/**
* A task positioner that also takes into account resizing a
@@ -49,11 +50,12 @@ class MultiDisplayVeiledResizeTaskPositioner(
private val desktopWindowDecoration: DesktopModeWindowDecoration,
private val displayController: DisplayController,
dragEventListener: DragPositioningCallbackUtility.DragEventListener,
- private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val transactionSupplier: () -> SurfaceControl.Transaction,
private val transitions: Transitions,
private val interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread private val handler: Handler,
-) : TaskPositioner, Transitions.TransitionHandler {
+ private val multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController,
+) : TaskPositioner, Transitions.TransitionHandler, DisplayController.OnDisplaysChangedListener {
private val dragEventListeners =
mutableListOf<DragPositioningCallbackUtility.DragEventListener>()
private val stableBounds = Rect()
@@ -71,6 +73,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
private var isResizingOrAnimatingResize = false
@Surface.Rotation private var rotation = 0
private var startDisplayId = 0
+ private val displayIds = mutableSetOf<Int>()
constructor(
taskOrganizer: ShellTaskOrganizer,
@@ -80,19 +83,22 @@ class MultiDisplayVeiledResizeTaskPositioner(
transitions: Transitions,
interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread handler: Handler,
+ multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController,
) : this(
taskOrganizer,
windowDecoration,
displayController,
dragEventListener,
- Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() },
+ { SurfaceControl.Transaction() },
transitions,
interactionJankMonitor,
handler,
+ multiDisplayDragMoveIndicatorController,
)
init {
dragEventListeners.add(dragEventListener)
+ displayController.addDisplayWindowListener(this)
}
override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
@@ -164,7 +170,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
)
- val t = transactionSupplier.get()
+ val t = transactionSupplier()
val startDisplayLayout = displayController.getDisplayLayout(startDisplayId)
val currentDisplayLayout = displayController.getDisplayLayout(displayId)
@@ -196,7 +202,13 @@ class MultiDisplayVeiledResizeTaskPositioner(
)
)
- // TODO(b/383069173): Render drag indicator(s)
+ multiDisplayDragMoveIndicatorController.onDragMove(
+ boundsDp,
+ startDisplayId,
+ desktopWindowDecoration.mTaskInfo,
+ displayIds,
+ transactionSupplier,
+ )
t.setPosition(
desktopWindowDecoration.leash,
@@ -267,7 +279,10 @@ class MultiDisplayVeiledResizeTaskPositioner(
)
)
- // TODO(b/383069173): Clear drag indicator(s)
+ multiDisplayDragMoveIndicatorController.onDragEnd(
+ desktopWindowDecoration.mTaskInfo.taskId,
+ transactionSupplier,
+ )
}
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
@@ -348,6 +363,14 @@ class MultiDisplayVeiledResizeTaskPositioner(
dragEventListeners.remove(dragEventListener)
}
+ override fun onTopologyChanged(topology: DisplayTopology) {
+ // TODO: b/383069173 - Cancel window drag when topology changes happen during drag.
+
+ displayIds.clear()
+ val displayBounds = topology.getAbsoluteBounds()
+ displayIds.addAll(List(displayBounds.size()) { displayBounds.keyAt(it) })
+ }
+
companion object {
// Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
// timing out in the middle of a resize or drag action.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt
new file mode 100644
index 000000000000..abd238847519
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.graphics.RectF
+import android.testing.TestableResources
+import android.view.Display
+import android.view.SurfaceControl
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import java.util.function.Supplier
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for [MultiDisplayDragMoveIndicatorController].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:MultiDisplayDragMoveIndicatorControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
+ private val displayController = mock<DisplayController>()
+ private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
+ private val indicatorSurfaceFactory = mock<MultiDisplayDragMoveIndicatorSurface.Factory>()
+ private val indicatorSurface0 = mock<MultiDisplayDragMoveIndicatorSurface>()
+ private val indicatorSurface1 = mock<MultiDisplayDragMoveIndicatorSurface>()
+ private val transaction = mock<SurfaceControl.Transaction>()
+ private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>()
+ private val taskInfo = mock<RunningTaskInfo>()
+ private val display0 = mock<Display>()
+ private val display1 = mock<Display>()
+
+ private lateinit var resources: TestableResources
+ private val executor = TestShellExecutor()
+
+ private lateinit var controller: MultiDisplayDragMoveIndicatorController
+
+ @Before
+ fun setUp() {
+ resources = mContext.getOrCreateTestableResources()
+ val resourceConfiguration = Configuration()
+ resourceConfiguration.uiMode = 0
+ resources.overrideConfiguration(resourceConfiguration)
+
+ controller =
+ MultiDisplayDragMoveIndicatorController(
+ displayController,
+ rootTaskDisplayAreaOrganizer,
+ indicatorSurfaceFactory,
+ executor,
+ )
+
+ val spyDisplayLayout0 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0,
+ MultiDisplayTestUtil.DISPLAY_DPI_0,
+ resources.resources,
+ )
+ val spyDisplayLayout1 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
+ MultiDisplayTestUtil.DISPLAY_DPI_1,
+ resources.resources,
+ )
+
+ taskInfo.taskId = TASK_ID
+ whenever(displayController.getDisplayLayout(0)).thenReturn(spyDisplayLayout0)
+ whenever(displayController.getDisplayLayout(1)).thenReturn(spyDisplayLayout1)
+ whenever(displayController.getDisplay(0)).thenReturn(display0)
+ whenever(displayController.getDisplay(1)).thenReturn(display1)
+ whenever(indicatorSurfaceFactory.create(taskInfo, display0)).thenReturn(indicatorSurface0)
+ whenever(indicatorSurfaceFactory.create(taskInfo, display1)).thenReturn(indicatorSurface1)
+ whenever(transactionSupplier.get()).thenReturn(transaction)
+ }
+
+ @Test
+ fun onDrag_boundsNotIntersectWithDisplay_noIndicator() {
+ controller.onDragMove(
+ RectF(2000f, 2000f, 2100f, 2200f), // not intersect with any display
+ startDisplayId = 0,
+ taskInfo,
+ displayIds = setOf(0, 1),
+ ) { transaction }
+ executor.flushAll()
+
+ verify(indicatorSurfaceFactory, never()).create(any(), any())
+ }
+
+ @Test
+ fun onDrag_boundsIntersectWithStartDisplay_noIndicator() {
+ controller.onDragMove(
+ RectF(100f, 100f, 200f, 200f), // intersect with display 0
+ startDisplayId = 0,
+ taskInfo,
+ displayIds = setOf(0, 1),
+ ) { transaction }
+ executor.flushAll()
+
+ verify(indicatorSurfaceFactory, never()).create(any(), any())
+ }
+
+ @Test
+ fun onDrag_boundsIntersectWithNonStartDisplay_showAndDisposeIndicator() {
+ controller.onDragMove(
+ RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1
+ startDisplayId = 0,
+ taskInfo,
+ displayIds = setOf(0, 1),
+ ) { transaction }
+ executor.flushAll()
+
+ verify(indicatorSurfaceFactory, times(1)).create(taskInfo, display1)
+ verify(indicatorSurface1, times(1))
+ .show(transaction, taskInfo, rootTaskDisplayAreaOrganizer, 1, Rect(0, 1800, 200, 2400))
+
+ controller.onDragMove(
+ RectF(2000f, 2000f, 2100f, 2200f), // not intersect with display 1
+ startDisplayId = 0,
+ taskInfo,
+ displayIds = setOf(0, 1)
+ ) { transaction }
+ while (executor.callbacks.isNotEmpty()) {
+ executor.flushAll()
+ }
+
+ verify(indicatorSurface1, times(1))
+ .relayout(any(), eq(transaction), shouldBeVisible = eq(false))
+
+ controller.onDragEnd(TASK_ID, { transaction })
+ while (executor.callbacks.isNotEmpty()) {
+ executor.flushAll()
+ }
+
+ verify(indicatorSurface1, times(1)).disposeSurface(transaction)
+ }
+
+ companion object {
+ private const val TASK_ID = 10
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index 0d5741fccbcc..8ad54f5a0bb4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -16,51 +16,28 @@
package com.android.wm.shell.desktopmode
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
-import android.content.ContentResolver
-import android.os.Binder
import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
-import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
-import android.view.IWindowManager
-import android.view.WindowManager.TRANSIT_CHANGE
-import android.window.DisplayAreaInfo
-import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
-import com.android.wm.shell.MockToken
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.transition.Transitions
-import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
-import org.mockito.Mockito.anyInt
import org.mockito.Mockito.spy
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -73,27 +50,18 @@ import org.mockito.quality.Strictness
@RunWith(AndroidTestingRunner::class)
class DesktopDisplayEventHandlerTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
- @Mock lateinit var transitions: Transitions
@Mock lateinit var displayController: DisplayController
- @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock private lateinit var mockWindowManager: IWindowManager
@Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
@Mock private lateinit var mockDesktopRepository: DesktopRepository
@Mock private lateinit var mockDesktopTasksController: DesktopTasksController
- @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
private lateinit var handler: DesktopDisplayEventHandler
private val onDisplaysChangedListenerCaptor = argumentCaptor<OnDisplaysChangedListener>()
- private val runningTasks = mutableListOf<RunningTaskInfo>()
private val externalDisplayId = 100
- private val freeformTask =
- TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()
- private val fullscreenTask =
- TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
- private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
@Before
fun setUp() {
@@ -105,24 +73,15 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
shellInit = spy(ShellInit(testExecutor))
whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
- whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
- .thenReturn(defaultTDA)
handler =
DesktopDisplayEventHandler(
context,
shellInit,
- transitions,
displayController,
- rootTaskDisplayAreaOrganizer,
- mockWindowManager,
mockDesktopUserRepositories,
mockDesktopTasksController,
- shellTaskOrganizer,
+ desktopDisplayModeController,
)
- whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
- runningTasks.add(freeformTask)
- runningTasks.add(fullscreenTask)
shellInit.init()
verify(displayController)
.addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture())
@@ -133,65 +92,6 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
mockitoSession.finishMocking()
}
- private fun testDisplayWindowingModeSwitch(
- defaultWindowingMode: Int,
- extendedDisplayEnabled: Boolean,
- expectTransition: Boolean,
- ) {
- defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
- whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
- val settingsSession =
- ExtendedDisplaySettingsSession(
- context.contentResolver,
- if (extendedDisplayEnabled) 1 else 0,
- )
-
- settingsSession.use {
- connectExternalDisplay()
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- disconnectExternalDisplay()
-
- if (expectTransition) {
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(2))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
- .isEqualTo(defaultWindowingMode)
- } else {
- verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
- }
- }
- }
-
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = false,
- expectTransition = false,
- )
- }
-
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = true,
- expectTransition = true,
- )
- }
-
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FREEFORM,
- extendedDisplayEnabled = true,
- expectTransition = false,
- )
- }
-
@Test
fun testDisplayAdded_supportsDesks_createsDesk() {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -231,70 +131,14 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
}
@Test
- fun displayWindowingModeSwitch_existingTasksOnConnected() {
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
- WINDOWING_MODE_FULLSCREEN
- }
-
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- connectExternalDisplay()
-
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- }
- }
-
- @Test
- fun displayWindowingModeSwitch_existingTasksOnDisconnected() {
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
- WINDOWING_MODE_FULLSCREEN
- }
-
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- disconnectExternalDisplay()
-
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
- }
-
- private fun connectExternalDisplay() {
- whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
+ fun testConnectExternalDisplay() {
onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
+ verify(desktopDisplayModeController).refreshDisplayWindowingMode()
}
- private fun disconnectExternalDisplay() {
- whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
- .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ @Test
+ fun testDisconnectExternalDisplay() {
onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
- }
-
- private class ExtendedDisplaySettingsSession(
- private val contentResolver: ContentResolver,
- private val overrideValue: Int,
- ) : AutoCloseable {
- private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
- private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
-
- init {
- Settings.Global.putInt(contentResolver, settingName, overrideValue)
- }
-
- override fun close() {
- Settings.Global.putInt(contentResolver, settingName, initialValue)
- }
+ verify(desktopDisplayModeController).refreshDisplayWindowingMode()
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
new file mode 100644
index 000000000000..0ff7230f6e0c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.content.ContentResolver
+import android.os.Binder
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [DesktopDisplayModeController]
+ *
+ * Usage: atest WMShellUnitTests:DesktopDisplayModeControllerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopDisplayModeControllerTest : ShellTestCase() {
+ private val transitions = mock<Transitions>()
+ private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
+ private val mockWindowManager = mock<IWindowManager>()
+ private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+ private val desktopWallpaperActivityTokenProvider =
+ mock<DesktopWallpaperActivityTokenProvider>()
+
+ private lateinit var controller: DesktopDisplayModeController
+
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val freeformTask =
+ TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()
+ private val fullscreenTask =
+ TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
+ private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ private val wallpaperToken = MockToken().token()
+
+ @Before
+ fun setUp() {
+ whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder())
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultTDA)
+ controller =
+ DesktopDisplayModeController(
+ context,
+ transitions,
+ rootTaskDisplayAreaOrganizer,
+ mockWindowManager,
+ shellTaskOrganizer,
+ desktopWallpaperActivityTokenProvider,
+ )
+ runningTasks.add(freeformTask)
+ runningTasks.add(fullscreenTask)
+ whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks))
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
+ }
+
+ private fun testDisplayWindowingModeSwitch(
+ defaultWindowingMode: Int,
+ extendedDisplayEnabled: Boolean,
+ expectTransition: Boolean,
+ ) {
+ defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode)
+ val settingsSession =
+ ExtendedDisplaySettingsSession(
+ context.contentResolver,
+ if (extendedDisplayEnabled) 1 else 0,
+ )
+
+ settingsSession.use {
+ connectExternalDisplay()
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ disconnectExternalDisplay()
+
+ if (expectTransition) {
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(2))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(defaultWindowingMode)
+ assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ } else {
+ verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
+ }
+ }
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = false,
+ expectTransition = false,
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = true,
+ expectTransition = true,
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ extendedDisplayEnabled = true,
+ expectTransition = false,
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitch_existingTasksOnConnected() {
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
+
+ ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
+ connectExternalDisplay()
+
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+ }
+
+ @Test
+ fun displayWindowingModeSwitch_existingTasksOnDisconnected() {
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
+ WINDOWING_MODE_FULLSCREEN
+ }
+
+ ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
+ disconnectExternalDisplay()
+
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+ }
+
+ private fun connectExternalDisplay() {
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, EXTERNAL_DISPLAY_ID))
+ controller.refreshDisplayWindowingMode()
+ }
+
+ private fun disconnectExternalDisplay() {
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ controller.refreshDisplayWindowingMode()
+ }
+
+ private class ExtendedDisplaySettingsSession(
+ private val contentResolver: ContentResolver,
+ private val overrideValue: Int,
+ ) : AutoCloseable {
+ private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+ private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+ init {
+ Settings.Global.putInt(contentResolver, settingName, overrideValue)
+ }
+
+ override fun close() {
+ Settings.Global.putInt(contentResolver, settingName, initialValue)
+ }
+ }
+
+ private companion object {
+ const val EXTERNAL_DISPLAY_ID = 100
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index fd5e567f69ed..c8025a94e453 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -64,7 +64,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.os.IResultReceiver;
-import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -73,6 +72,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.shared.R;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index f69bf34ea3f7..88c6e499b869 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -16,13 +16,16 @@
package com.android.wm.shell.shared.desktopmode
+import android.Manifest.permission.SYSTEM_ALERT_WINDOW
import android.app.TaskInfo
import android.compat.testing.PlatformCompatChangeRule
import android.content.ComponentName
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Process
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -39,7 +42,9 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -55,6 +60,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
private val packageManager: PackageManager = mock()
private val homeActivities = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
+ private val baseActivityTest = ComponentName("com.test.dummypackage", "TestClass")
@Before
fun setUp() {
@@ -64,6 +70,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
@@ -71,10 +78,39 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
+ baseActivity = baseActivityTest
}))
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
+ fun testIsTopActivityExemptWithPermission_onlyTransparentActivitiesInStack() {
+ allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW))
+ assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ baseActivity = baseActivityTest
+ }))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
+ fun testIsTopActivityExemptWithNoPermission_onlyTransparentActivitiesInStack() {
+ allowOverlayPermission(arrayOf())
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ baseActivity = baseActivityTest
+ }))
+ }
+
+ @Test
fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() {
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
@@ -219,4 +255,15 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
}
}
}
+
+ fun allowOverlayPermission(permissions: Array<String>) {
+ val packageInfo = mock<PackageInfo>()
+ packageInfo.requestedPermissions = permissions
+ whenever(
+ packageManager.getPackageInfo(
+ anyString(),
+ eq(PackageManager.GET_PERMISSIONS)
+ )
+ ).thenReturn(packageInfo)
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 8cccdb2b6120..81dfaed56b6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -52,6 +52,7 @@ import com.android.wm.shell.common.DisplayChangeController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
@@ -138,6 +139,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>()
protected val mockActivityOrientationChangeHandler =
mock<DesktopActivityOrientationChangeHandler>()
+ protected val mockMultiDisplayDragMoveIndicatorController =
+ mock<MultiDisplayDragMoveIndicatorController>()
protected val mockInputManager = mock<InputManager>()
private val mockTaskPositionerFactory =
mock<DesktopModeWindowDecorViewModel.TaskPositionerFactory>()
@@ -229,6 +232,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockRecentsTransitionHandler,
desktopModeCompatPolicy,
mockTilingWindowDecoration,
+ mockMultiDisplayDragMoveIndicatorController,
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -243,6 +247,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
any(),
any(),
any(),
+ any(),
any()
)
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index 937938df82c8..a6b077037b86 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -41,6 +41,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
import com.android.wm.shell.common.MultiDisplayTestUtil
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
@@ -62,8 +63,8 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
/**
@@ -93,7 +94,8 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
@Mock private lateinit var mockTransitions: Transitions
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockSurfaceControl: SurfaceControl
-
+ @Mock private lateinit var mockMultiDisplayDragMoveIndicatorController:
+ MultiDisplayDragMoveIndicatorController
private lateinit var resources: TestableResources
private lateinit var spyDisplayLayout0: DisplayLayout
private lateinit var spyDisplayLayout1: DisplayLayout
@@ -170,10 +172,11 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration,
mockDisplayController,
mockDragEventListener,
- mockTransactionFactory,
+ { mockTransaction },
mockTransitions,
mockInteractionJankMonitor,
mainHandler,
+ mockMultiDisplayDragMoveIndicatorController,
)
}
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 4fe0b80f3951..275972495206 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -715,47 +715,45 @@ void ASurfaceTransaction_setLuts(ASurfaceTransaction* aSurfaceTransaction,
sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
- int fd = -1;
+ base::unique_fd fd;
std::vector<int32_t> offsets;
std::vector<int32_t> dimensions;
std::vector<int32_t> sizes;
std::vector<int32_t> samplingKeys;
if (luts) {
- std::vector<float> buffer(luts->totalBufferSize);
int32_t count = luts->offsets.size();
offsets = luts->offsets;
dimensions.reserve(count);
sizes.reserve(count);
samplingKeys.reserve(count);
- for (int32_t i = 0; i < count; i++) {
- dimensions.emplace_back(luts->entries[i]->properties.dimension);
- sizes.emplace_back(luts->entries[i]->properties.size);
- samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey);
- std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(),
- buffer.begin() + offsets[i]);
- }
// mmap
- fd = ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float));
+ fd.reset(ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float)));
if (fd < 0) {
LOG_ALWAYS_FATAL("setLuts, ashmem_create_region() failed");
return;
}
- void* ptr = mmap(nullptr, luts->totalBufferSize * sizeof(float), PROT_READ | PROT_WRITE,
- MAP_SHARED, fd, 0);
+ float* ptr = (float*)mmap(nullptr, luts->totalBufferSize * sizeof(float),
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
LOG_ALWAYS_FATAL("setLuts, Failed to map the shared memory");
return;
}
- memcpy(ptr, buffer.data(), luts->totalBufferSize * sizeof(float));
+ for (int32_t i = 0; i < count; i++) {
+ dimensions.emplace_back(luts->entries[i]->properties.dimension);
+ sizes.emplace_back(luts->entries[i]->properties.size);
+ samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey);
+ std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(),
+ ptr + offsets[i]);
+ }
+
munmap(ptr, luts->totalBufferSize * sizeof(float));
}
- transaction->setLuts(surfaceControl, base::unique_fd(fd), offsets, dimensions, sizes,
- samplingKeys);
+ transaction->setLuts(surfaceControl, std::move(fd), offsets, dimensions, sizes, samplingKeys);
}
void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index bd892637a5eb..853d1ad0dc94 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -152,34 +152,6 @@ android_app {
}
//##########################################################
-// Variant: System app upgrade
-
-android_app {
- name: "CtsShimUpgrade",
-
- sdk_version: "current",
- optimize: {
- enabled: false,
- },
- dex_preopt: {
- enabled: false,
- },
-
- manifest: "shim/AndroidManifestUpgrade.xml",
- min_sdk_version: "24",
-}
-
-java_genrule {
- name: "generate_shim_manifest",
- srcs: [
- "shim/AndroidManifest.xml",
- ":CtsShimUpgrade",
- ],
- out: ["AndroidManifest.xml"],
- cmd: "sed -e s/__HASH__/`sha512sum -b $(location :CtsShimUpgrade) | cut -d' ' -f1`/ $(location shim/AndroidManifest.xml) > $(out)",
-}
-
-//##########################################################
// Variant: System app
android_app {
@@ -193,7 +165,7 @@ android_app {
enabled: false,
},
- manifest: ":generate_shim_manifest",
+ manifest: "shim/AndroidManifest.xml",
apex_available: [
"//apex_available:platform",
"com.android.apex.cts.shim.v1",
diff --git a/packages/CtsShim/build/shim/AndroidManifest.xml b/packages/CtsShim/build/shim/AndroidManifest.xml
index 3b8276b66bac..1ffe56c0c76a 100644
--- a/packages/CtsShim/build/shim/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim/AndroidManifest.xml
@@ -17,15 +17,13 @@
<!-- Manifest for the system CTS shim -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.android.cts.ctsshim"
- android:sharedUserId="com.android.cts.ctsshim" >
+ package="com.android.cts.ctsshim" >
- <uses-sdk
- android:minSdkVersion="24"
+ <uses-sdk android:minSdkVersion="24"
android:targetSdkVersion="28" />
<restrict-update
- android:hash="__HASH__" />
+ android:hash="__CAN_NOT_BE_UPDATED__" />
<application
android:hasCode="false"
diff --git a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml b/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml
deleted file mode 100644
index 7f3644a18ad6..000000000000
--- a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!-- Manifest for the system CTS shim -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.cts.ctsshim" >
-
- <uses-sdk
- android:minSdkVersion="24"
- android:targetSdkVersion="28" />
-
- <application
- android:hasCode="false"
- tools:ignore="AllowBackup,MissingApplicationIcon" />
-</manifest>
-
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 182daeb45d7c..203a3bf64910 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -31,5 +31,6 @@ android_library {
apex_available: [
"//apex_available:platform",
"com.android.healthfitness",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml
new file mode 100644
index 000000000000..c999de7d99ea
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml
@@ -0,0 +1,157 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="112dp"
+ android:height="112dp"
+ android:viewportHeight="112"
+ android:viewportWidth="112">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="56.5"
+ android:translateY="56.625">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="?android:attr/textColorPrimary"
+ android:fillType="nonZero"
+ android:pathData=" M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:trimPathEnd="1"
+ android:trimPathOffset="0"
+ android:trimPathStart="0" />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:rotation="-90"
+ android:translateX="56"
+ android:translateY="56">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:pathData=" M53.5 0 C53.5,-29.53 29.53,-53.5 0,-53.5 C0,-53.5 0,-53.5 0,-53.5 C-29.53,-53.5 -53.5,-29.53 -53.5,0 C-53.5,0 -53.5,0 -53.5,0 C-53.5,29.53 -29.53,53.5 0,53.5 C0,53.5 0,53.5 0,53.5 C29.53,53.5 53.5,29.53 53.5,0 C53.5,0 53.5,0 53.5,0c "
+ android:strokeAlpha="1"
+ android:strokeColor="?android:attr/textColorTertiary"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="5"
+ android:trimPathEnd="0"
+ android:trimPathOffset="0"
+ android:trimPathStart="0" />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="fillAlpha"
+ android:startOffset="400"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="pathData"
+ android:startOffset="400"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="483"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 14.77,-13.41 14.77,-13.41 C14.77,-13.41 17.59,-10.59 17.59,-10.59 C17.59,-10.59 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="trimPathEnd"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
index b10ef6e77ec7..c448a2d434f8 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
@@ -28,81 +28,104 @@
android:orientation="vertical"
style="@style/Banner.Preference.SettingsLib.Expressive">
- <RelativeLayout
- android:id="@+id/top_row"
+ <FrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:layout_height="wrap_content">
<LinearLayout
+ android:id="@+id/banner_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
android:orientation="vertical">
- <TextView
- android:id="@+id/banner_header"
+
+ <RelativeLayout
+ android:id="@+id/top_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Header.SettingsLib.Expressive"/>
+ android:orientation="horizontal">
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/banner_icon"
- android:layout_width="@dimen/settingslib_expressive_space_small3"
- android:layout_height="@dimen/settingslib_expressive_space_small3"
- android:layout_gravity="center_vertical"
- android:importantForAccessibility="no"
- android:scaleType="fitCenter" />
-
- <TextView
- android:id="@+id/banner_title"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Title.SettingsLib.Expressive" />
- </LinearLayout>
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/banner_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Header.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="@dimen/settingslib_expressive_space_small3"
+ android:layout_height="@dimen/settingslib_expressive_space_small3"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall4"
+ android:importantForAccessibility="no"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/banner_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Title.SettingsLib.Expressive" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/banner_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ </LinearLayout>
+
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/accessibility_banner_message_dismiss"
+ style="@style/Banner.Dismiss.SettingsLib.Expressive" />
+ </RelativeLayout>
<TextView
- android:id="@+id/banner_subtitle"
+ android:id="@+id/banner_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Summary.SettingsLib.Expressive"/>
+
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ android:id="@+id/banner_buttons_frame"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
+ android:orientation="horizontal">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_negative_btn"
+ android:layout_weight="1"
+ style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
+ <Space
+ android:id="@+id/banner_button_space"
+ android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_height="@dimen/settingslib_expressive_space_small1"/>
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_positive_btn"
+ android:layout_weight="1"
+ style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
+ </LinearLayout>
</LinearLayout>
- <ImageButton
- android:id="@+id/banner_dismiss_btn"
+ <TextView
+ android:id="@+id/resolved_banner_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.Dismiss.SettingsLib.Expressive" />
- </RelativeLayout>
-
- <TextView
- android:id="@+id/banner_summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@style/Banner.Summary.SettingsLib.Expressive"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/banner_buttons_frame"
- android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
- android:orientation="horizontal">
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/banner_negative_btn"
- android:layout_weight="1"
- style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
- <Space
- android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
- android:layout_height="@dimen/settingslib_expressive_space_small1"/>
- <com.google.android.material.button.MaterialButton
- android:id="@+id/banner_positive_btn"
- android:layout_weight="1"
- style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
- </LinearLayout>
+ android:drawableTop="@drawable/settingslib_resolved_banner_avd"
+ android:visibility="gone"
+ style="@style/Banner.ResolvedText.SettingsLib.Expressive"/>
+ </FrameLayout>
</com.android.settingslib.widget.BannerMessageView>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
index b864311bf9b7..09e07ccef683 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
@@ -33,7 +33,6 @@
<style name="Banner.Title.SettingsLib.Expressive"
parent="">
<item name="android:layout_gravity">start</item>
- <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item>
<item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -73,4 +72,11 @@
parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra">
<item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
</style>
+
+ <style name="Banner.ResolvedText.SettingsLib.Expressive" parent="">
+ <item name="android:layout_gravity">center</item>
+ <item name="android:drawablePadding">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index c82829d6ccea..c90a76a39510 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -35,6 +35,8 @@ import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.preference.Preference;
@@ -43,6 +45,9 @@ import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.banner.R;
import com.google.android.material.button.MaterialButton;
+
+import java.util.Objects;
+
/**
* Banner message is a banner displaying important information (permission request, page error etc),
* and provide actions for user to address. It requires a user action to be dismissed.
@@ -67,13 +72,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
R.color.banner_accent_attention_normal,
R.color.settingslib_banner_button_background_normal);
- // Corresponds to the enum valye of R.attr.attentionLevel
+ // Corresponds to the enum value of R.attr.attentionLevel
private final int mAttrValue;
@ColorRes private final int mBackgroundColorResId;
@ColorRes private final int mAccentColorResId;
@ColorRes private final int mButtonBackgroundColorResId;
- AttentionLevel(int attrValue, @ColorRes int backgroundColorResId,
+ AttentionLevel(
+ int attrValue,
+ @ColorRes int backgroundColorResId,
@ColorRes int accentColorResId,
@ColorRes int buttonBackgroundColorResId) {
mAttrValue = attrValue;
@@ -115,33 +122,38 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
new BannerMessagePreference.DismissButtonInfo();
// Default attention level is High.
- private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
- private CharSequence mSubtitle;
- private CharSequence mHeader;
+ @NonNull private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
+ @Nullable private CharSequence mSubtitle;
+ @Nullable private CharSequence mHeader;
private int mButtonOrientation;
+ @Nullable private ResolutionAnimator.Data mResolutionData;
- public BannerMessagePreference(Context context) {
+ public BannerMessagePreference(@NonNull Context context) {
super(context);
init(context, null /* attrs */);
}
- public BannerMessagePreference(Context context, AttributeSet attrs) {
+ public BannerMessagePreference(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
- public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ public BannerMessagePreference(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
- public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr,
+ public BannerMessagePreference(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
- private void init(Context context, AttributeSet attrs) {
+ private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
setSelectable(false);
int resId = SettingsThemeHelper.isExpressiveTheme(context)
? R.layout.settingslib_expressive_banner_message
@@ -166,7 +178,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
@Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final Context context = getContext();
@@ -193,14 +205,20 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
final ImageView iconView = (ImageView) holder.findViewById(R.id.banner_icon);
if (iconView != null) {
Drawable icon = getIcon();
- iconView.setImageDrawable(
- icon == null
- ? getContext().getDrawable(R.drawable.ic_warning)
- : icon);
- if (mAttentionLevel != AttentionLevel.NORMAL
- && !SettingsThemeHelper.isExpressiveTheme(context)) {
- iconView.setColorFilter(
- new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+
+ if (icon == null && SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setVisibility(View.GONE);
+ } else {
+ iconView.setVisibility(View.VISIBLE);
+ iconView.setImageDrawable(
+ icon == null
+ ? getContext().getDrawable(R.drawable.ic_warning)
+ : icon);
+ if (mAttentionLevel != AttentionLevel.NORMAL
+ && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setColorFilter(
+ new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ }
}
}
@@ -233,8 +251,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
mDismissButtonInfo.setUpButton();
final TextView subtitleView = (TextView) holder.findViewById(R.id.banner_subtitle);
- subtitleView.setText(mSubtitle);
- subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ if (subtitleView != null) {
+ subtitleView.setText(mSubtitle);
+ subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ }
TextView headerView = (TextView) holder.findViewById(R.id.banner_header);
if (headerView != null) {
@@ -268,11 +288,25 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
linearLayout.setOrientation(mButtonOrientation);
}
}
+
+ View buttonSpace = holder.findViewById(R.id.banner_button_space);
+ if (buttonSpace != null) {
+ if (mPositiveButtonInfo.shouldBeVisible() && mNegativeButtonInfo.shouldBeVisible()) {
+ buttonSpace.setVisibility(View.VISIBLE);
+ } else {
+ buttonSpace.setVisibility(View.GONE);
+ }
+ }
+
+ if (mResolutionData != null) {
+ new ResolutionAnimator(mResolutionData, holder).startResolutionAnimation();
+ }
}
/**
- * Set the visibility state of positive button.
+ * Sets the visibility state of the positive button.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonVisible(boolean isVisible) {
if (isVisible != mPositiveButtonInfo.mIsVisible) {
mPositiveButtonInfo.mIsVisible = isVisible;
@@ -282,8 +316,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Set the visibility state of negative button.
+ * Sets the visibility state of the negative button.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonVisible(boolean isVisible) {
if (isVisible != mNegativeButtonInfo.mIsVisible) {
mNegativeButtonInfo.mIsVisible = isVisible;
@@ -293,9 +328,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Set the visibility state of dismiss button.
+ * Sets the visibility state of the dismiss button.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setDismissButtonVisible(boolean isVisible) {
if (isVisible != mDismissButtonInfo.mIsVisible) {
mDismissButtonInfo.mIsVisible = isVisible;
@@ -305,10 +341,35 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when positive button is clicked.
+ * Sets the enabled state of the positive button.
+ */
+ @NonNull
+ public BannerMessagePreference setPositiveButtonEnabled(boolean isEnabled) {
+ if (isEnabled != mPositiveButtonInfo.mIsEnabled) {
+ mPositiveButtonInfo.mIsEnabled = isEnabled;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
+ * Sets the enabled state of the negative button.
+ */
+ @NonNull
+ public BannerMessagePreference setNegativeButtonEnabled(boolean isEnabled) {
+ if (isEnabled != mNegativeButtonInfo.mIsEnabled) {
+ mNegativeButtonInfo.mIsEnabled = isEnabled;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
+ * Registers a callback to be invoked when positive button is clicked.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mPositiveButtonInfo.mListener) {
mPositiveButtonInfo.mListener = listener;
notifyChanged();
@@ -317,10 +378,11 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when negative button is clicked.
+ * Registers a callback to be invoked when negative button is clicked.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mNegativeButtonInfo.mListener) {
mNegativeButtonInfo.mListener = listener;
notifyChanged();
@@ -329,11 +391,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when the dismiss button is clicked.
+ * Registers a callback to be invoked when the dismiss button is clicked.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setDismissButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mDismissButtonInfo.mListener) {
mDismissButtonInfo.mListener = listener;
notifyChanged();
@@ -344,6 +407,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in positive button.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonText(@StringRes int textResId) {
return setPositiveButtonText(getContext().getString(textResId));
}
@@ -351,7 +415,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in positive button.
*/
- public BannerMessagePreference setPositiveButtonText(CharSequence positiveButtonText) {
+ @NonNull
+ public BannerMessagePreference setPositiveButtonText(
+ @Nullable CharSequence positiveButtonText) {
if (!TextUtils.equals(positiveButtonText, mPositiveButtonInfo.mText)) {
mPositiveButtonInfo.mText = positiveButtonText;
notifyChanged();
@@ -362,6 +428,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in negative button.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonText(@StringRes int textResId) {
return setNegativeButtonText(getContext().getString(textResId));
}
@@ -369,7 +436,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in negative button.
*/
- public BannerMessagePreference setNegativeButtonText(CharSequence negativeButtonText) {
+ @NonNull
+ public BannerMessagePreference setNegativeButtonText(
+ @Nullable CharSequence negativeButtonText) {
if (!TextUtils.equals(negativeButtonText, mNegativeButtonInfo.mText)) {
mNegativeButtonInfo.mText = negativeButtonText;
notifyChanged();
@@ -380,8 +449,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets button orientation.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
public BannerMessagePreference setButtonOrientation(int orientation) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+
if (mButtonOrientation != orientation) {
mButtonOrientation = orientation;
notifyChanged();
@@ -393,6 +466,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setSubtitle(@StringRes int textResId) {
return setSubtitle(getContext().getString(textResId));
}
@@ -401,7 +475,8 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
- public BannerMessagePreference setSubtitle(CharSequence subtitle) {
+ @NonNull
+ public BannerMessagePreference setSubtitle(@Nullable CharSequence subtitle) {
if (!TextUtils.equals(subtitle, mSubtitle)) {
mSubtitle = subtitle;
notifyChanged();
@@ -412,7 +487,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the header.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
public BannerMessagePreference setHeader(@StringRes int textResId) {
return setHeader(getContext().getString(textResId));
}
@@ -420,8 +495,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the header.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public BannerMessagePreference setHeader(CharSequence header) {
+ @NonNull
+ public BannerMessagePreference setHeader(@Nullable CharSequence header) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+
if (!TextUtils.equals(header, mHeader)) {
mHeader = header;
notifyChanged();
@@ -430,32 +509,75 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Sets the attention level. This will update the color theme of the preference.
+ * Plays a resolution animation, showing the given message.
*/
- public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) {
- if (attentionLevel == mAttentionLevel) {
+ @NonNull
+ public BannerMessagePreference showResolutionAnimation(
+ @NonNull CharSequence resolutionMessage,
+ @NonNull ResolutionCompletedCallback onCompletionCallback) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
return this;
}
+ ResolutionAnimator.Data resolutionData =
+ new ResolutionAnimator.Data(resolutionMessage, onCompletionCallback);
+ if (!Objects.equals(mResolutionData, resolutionData)) {
+ mResolutionData = resolutionData;
+ notifyChanged();
+ }
+ return this;
+ }
- if (attentionLevel != null) {
- mAttentionLevel = attentionLevel;
+ /**
+ * Removes the resolution animation from this preference.
+ *
+ * <p>Should be called after the resolution animation completes if this preference will be
+ * reused. Otherwise the resolution animation will be played everytime this preference is
+ * displayed.
+ */
+ @NonNull
+ public BannerMessagePreference clearResolutionAnimation() {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+ if (mResolutionData != null) {
+ mResolutionData = null;
notifyChanged();
}
return this;
}
+ /**
+ * Sets the attention level. This will update the color theme of the preference.
+ */
+ @NonNull
+ public BannerMessagePreference setAttentionLevel(@NonNull AttentionLevel attentionLevel) {
+ if (attentionLevel == mAttentionLevel) {
+ return this;
+ }
+
+ mAttentionLevel = attentionLevel;
+ notifyChanged();
+ return this;
+ }
+
static class ButtonInfo {
- private Button mButton;
- private CharSequence mText;
- private View.OnClickListener mListener;
+ @Nullable private Button mButton;
+ @Nullable private CharSequence mText;
+ @Nullable private View.OnClickListener mListener;
private boolean mIsVisible = true;
+ private boolean mIsEnabled = true;
@ColorInt private int mColor;
@ColorInt private int mBackgroundColor;
- private ColorStateList mStrokeColor;
+ @Nullable private ColorStateList mStrokeColor;
void setUpButton() {
+ if (mButton == null) {
+ return;
+ }
+
mButton.setText(mText);
mButton.setOnClickListener(mListener);
+ mButton.setEnabled(mIsEnabled);
MaterialButton btn = null;
if (mButton instanceof MaterialButton) {
@@ -492,11 +614,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
static class DismissButtonInfo {
- private ImageButton mButton;
- private View.OnClickListener mListener;
+ @Nullable private ImageButton mButton;
+ @Nullable private View.OnClickListener mListener;
private boolean mIsVisible = true;
void setUpButton() {
+ if (mButton == null) {
+ return;
+ }
+
mButton.setOnClickListener(mListener);
if (shouldBeVisible()) {
mButton.setVisibility(View.VISIBLE);
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
index ff4e79ddaaa1..eabb6341c3dd 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
@@ -23,6 +23,7 @@ import android.view.TouchDelegate;
import android.view.View;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.widget.preference.banner.R;
@@ -34,22 +35,25 @@ import com.android.settingslib.widget.preference.banner.R;
* {@link BannerMessagePreference} to a {@code PreferenceScreen}.
*/
public class BannerMessageView extends LinearLayout {
- private Rect mTouchTargetForDismissButton;
+ @Nullable private Rect mTouchTargetForDismissButton;
- public BannerMessageView(Context context) {
+ public BannerMessageView(@NonNull Context context) {
super(context);
}
- public BannerMessageView(Context context,
- @Nullable AttributeSet attrs) {
+ public BannerMessageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
- public BannerMessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ public BannerMessageView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public BannerMessageView(Context context, AttributeSet attrs, int defStyleAttr,
+ public BannerMessageView(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt
new file mode 100644
index 000000000000..fbf910a42423
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.provider.DeviceConfig
+import android.transition.Fade
+import android.transition.Transition
+import android.transition.TransitionListenerAdapter
+import android.transition.TransitionManager
+import android.transition.TransitionSet
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.LinearInterpolator
+import android.widget.TextView
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.banner.R
+import java.time.Duration
+
+/** Callback to communicate when a banner message resolution animation is completed. */
+fun interface ResolutionCompletedCallback {
+ fun onCompleted()
+}
+
+internal class ResolutionAnimator(
+ private val data: Data,
+ private val preferenceViewHolder: PreferenceViewHolder,
+) {
+
+ data class Data(
+ val resolutionMessage: CharSequence,
+ val resolutionCompletedCallback: ResolutionCompletedCallback,
+ )
+
+ private val defaultBannerContent: View?
+ get() = preferenceViewHolder.findView(R.id.banner_content)
+ private val resolvedTextView: TextView?
+ get() = preferenceViewHolder.findView(R.id.resolved_banner_text)
+
+ fun startResolutionAnimation() {
+ resolvedTextView?.text = data.resolutionMessage
+ resolvedTextView?.resolutionDrawable?.reset()
+
+ val transitionSet =
+ TransitionSet()
+ .setOrdering(TransitionSet.ORDERING_SEQUENTIAL)
+ .setInterpolator(linearInterpolator)
+ .addTransition(hideIssueContentTransition)
+ .addTransition(
+ showResolvedContentTransition
+ .clone()
+ .addListener(
+ object : TransitionListenerAdapter() {
+ override fun onTransitionEnd(transition: Transition) {
+ super.onTransitionEnd(transition)
+ startIssueResolvedAnimation()
+ }
+ }
+ )
+ )
+
+ preferenceViewHolder.itemView.post {
+ TransitionManager.beginDelayedTransition(
+ preferenceViewHolder.itemView as ViewGroup,
+ transitionSet,
+ )
+
+ defaultBannerContent?.visibility = View.INVISIBLE
+ resolvedTextView?.visibility = View.VISIBLE
+ }
+
+ preferenceViewHolder.itemView.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {}
+
+ override fun onViewDetachedFromWindow(v: View) {
+ v.removeOnAttachStateChangeListener(this)
+ cancelAnimationsAndFinish()
+ }
+ }
+ )
+ }
+
+ private fun startIssueResolvedAnimation() {
+ val animatedDrawable = resolvedTextView?.resolutionDrawable
+
+ if (animatedDrawable == null) {
+ hideResolvedUiAndFinish()
+ return
+ }
+
+ animatedDrawable.apply {
+ clearAnimationCallbacks()
+ registerAnimationCallback(
+ object : Animatable2.AnimationCallback() {
+ override fun onAnimationEnd(drawable: Drawable) {
+ super.onAnimationEnd(drawable)
+ hideResolvedUiAndFinish()
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ private fun hideResolvedUiAndFinish() {
+ val hideTransition =
+ hideResolvedContentTransition
+ .clone()
+ .setInterpolator(linearInterpolator)
+ .addListener(
+ object : TransitionListenerAdapter() {
+ override fun onTransitionEnd(transition: Transition) {
+ super.onTransitionEnd(transition)
+ data.resolutionCompletedCallback.onCompleted()
+ }
+ }
+ )
+ TransitionManager.beginDelayedTransition(
+ preferenceViewHolder.itemView as ViewGroup,
+ hideTransition,
+ )
+ resolvedTextView?.visibility = View.GONE
+ }
+
+ private fun cancelAnimationsAndFinish() {
+ TransitionManager.endTransitions(preferenceViewHolder.itemView as ViewGroup)
+
+ resolvedTextView?.visibility = View.GONE
+
+ val animatedDrawable = resolvedTextView?.resolutionDrawable
+ animatedDrawable?.clearAnimationCallbacks()
+ animatedDrawable?.stop()
+
+ data.resolutionCompletedCallback.onCompleted()
+ }
+
+ private companion object {
+ private val linearInterpolator = LinearInterpolator()
+
+ private val HIDE_ISSUE_CONTENT_TRANSITION_DURATION = Duration.ofMillis(333)
+ private val hideIssueContentTransition =
+ Fade(Fade.OUT).setDuration(HIDE_ISSUE_CONTENT_TRANSITION_DURATION.toMillis())
+
+ private val SHOW_RESOLVED_CONTENT_TRANSITION_DELAY = Duration.ofMillis(133)
+ private val SHOW_RESOLVED_CONTENT_TRANSITION_DURATION = Duration.ofMillis(250)
+ private val showResolvedContentTransition =
+ Fade(Fade.IN)
+ .setStartDelay(SHOW_RESOLVED_CONTENT_TRANSITION_DELAY.toMillis())
+ .setDuration(SHOW_RESOLVED_CONTENT_TRANSITION_DURATION.toMillis())
+
+ private val hideResolvedContentTransitionDelay
+ get() =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Duration.ofMillis(
+ DeviceConfig.getLong(
+ "settings_ui",
+ "banner_message_pref_hide_resolved_content_delay_millis",
+ 400,
+ )
+ )
+ } else {
+ Duration.ofMillis(400)
+ }
+
+ private val HIDE_RESOLVED_UI_TRANSITION_DURATION = Duration.ofMillis(167)
+ private val hideResolvedContentTransition
+ get() =
+ Fade(Fade.OUT)
+ .setStartDelay(hideResolvedContentTransitionDelay.toMillis())
+ .setDuration(HIDE_RESOLVED_UI_TRANSITION_DURATION.toMillis())
+
+ inline fun <reified T : View> PreferenceViewHolder.findView(id: Int): T? =
+ findViewById(id) as? T
+
+ val TextView.resolutionDrawable: AnimatedVectorDrawable?
+ get() = compoundDrawables.find { it != null } as? AnimatedVectorDrawable
+ }
+}
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index a377f312ffbf..c8375a992d30 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -30,5 +30,6 @@ android_library {
apex_available: [
"//apex_available:platform",
"com.android.healthfitness",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index dc5c9b297181..b6e80c784f10 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -18,7 +18,7 @@
<resources>
<style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
<item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
- <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainerLowest</item>
+ <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
<item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
index 6a0632073de9..a04fce7eeb86 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
@@ -47,7 +47,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
private val mHandler = Handler(Looper.getMainLooper())
- private val syncRunnable = Runnable { updatePreferences() }
+ private val syncRunnable = Runnable { updatePreferencesList() }
init {
val context = preferenceGroup.context
@@ -64,7 +64,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
true, /* resolveRefs */
)
mLegacyBackgroundRes = outValue.resourceId
- updatePreferences()
+ updatePreferencesList()
}
@SuppressLint("RestrictedApi")
@@ -82,7 +82,7 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
updateBackground(holder, position)
}
- private fun updatePreferences() {
+ private fun updatePreferencesList() {
val oldList = ArrayList(mRoundCornerMappingList)
mRoundCornerMappingList = ArrayList()
mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup)
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
index 6015be8c894f..aa5e9d28cabe 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
@@ -23,10 +23,10 @@
<clip-path
android:pathData="
M0,0
- V13.5,0
- H13.5,20
- V0,20
- H0,0
+ H16
+ V12
+ H0
+ Z
M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
<path
android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
index fb73b6b253e1..f7bf8518a55c 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
@@ -8,10 +8,10 @@
<clip-path
android:pathData="
M0,0
- V13.5,0
- H13.5,20
- V0,20
- H0,0
+ H16
+ V12
+ H0
+ Z
M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
<path
android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
index 7c4c1c6b1126..016c614846e9 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
@@ -23,10 +23,10 @@
<clip-path
android:pathData="
M0,0
- V13.5,0
- H13.5,20
- V0,20
- H0,0
+ H16
+ V12
+ H0
+ Z
M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
<path
android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
index d23680d17b9c..b8304151eefe 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
@@ -23,10 +23,10 @@
<clip-path
android:pathData="
M0,0
- V13.5,0
- H13.5,20
- V0,20
- H0,0
+ H16
+ V12
+ H0
+ Z
M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
<path
android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 3017d79836e8..367e38ed779d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -63,6 +63,7 @@ import com.google.common.collect.ImmutableList;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -70,7 +71,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -108,6 +108,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
private static final String SYSUI_PKG = "com.android.systemui";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
+ private static final String VALID_PASSWORD_CHARACTERS =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,"
+ + ".<>?/";
+ private static final int PASSWORD_LENGTH = 16;
static final String NAME = "LE_AUDIO_BROADCAST";
private static final String UNDERLINE = "_";
@@ -1088,11 +1092,16 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
}
- private String generateRandomPassword() {
- String randomUUID = UUID.randomUUID().toString();
- // first 16 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
- return randomUUID.substring(0, 8) + randomUUID.substring(9, 13) + randomUUID.substring(14,
- 18);
+ private static String generateRandomPassword() {
+ SecureRandom random = new SecureRandom();
+ StringBuilder stringBuilder = new StringBuilder(PASSWORD_LENGTH);
+
+ for (int i = 0; i < PASSWORD_LENGTH; i++) {
+ int randomIndex = random.nextInt(VALID_PASSWORD_CHARACTERS.length());
+ stringBuilder.append(VALID_PASSWORD_CHARACTERS.charAt(randomIndex));
+ }
+
+ return stringBuilder.toString();
}
private void registerContentObserver() {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 85617bad1a91..70c042cb8eba 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -811,7 +811,7 @@ public class SettingsBackupTest {
Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
Settings.Secure.V_TO_U_RESTORE_DENYLIST,
Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
- Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY);
+ Settings.Secure.OTP_NOTIFICATION_REDACTION_LOCK_TIME);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 5e12ee1b18fa..db9035b1635b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -65,6 +65,7 @@ import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateElementFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
+import com.android.compose.theme.colorAttr
import com.android.settingslib.Utils
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -75,8 +76,6 @@ import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipBackground
-import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipHighlighted
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim
import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingHorizontal
import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingVertical
@@ -107,7 +106,7 @@ object ShadeHeader {
object Dimensions {
val CollapsedHeight = 48.dp
val ExpandedHeight = 120.dp
- val ChipPaddingHorizontal = 8.dp
+ val ChipPaddingHorizontal = 6.dp
val ChipPaddingVertical = 4.dp
}
@@ -117,12 +116,6 @@ object ShadeHeader {
val ColorScheme.onScrimDim: Color
get() = Color.DarkGray
-
- val ColorScheme.chipBackground: Color
- get() = Color.DarkGray
-
- val ColorScheme.chipHighlighted: Color
- get() = Color.LightGray
}
object TestTags {
@@ -165,7 +158,7 @@ fun ContentScope.CollapsedShadeHeader(
VariableDayDate(
longerDateText = viewModel.longerDateText,
shorterDateText = viewModel.shorterDateText,
- chipHighlight = viewModel.notificationsChipHighlight,
+ textColor = colorAttr(android.R.attr.textColorPrimary),
modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart),
)
}
@@ -265,7 +258,7 @@ fun ContentScope.ExpandedShadeHeader(
VariableDayDate(
longerDateText = viewModel.longerDateText,
shorterDateText = viewModel.shorterDateText,
- chipHighlight = viewModel.notificationsChipHighlight,
+ textColor = colorAttr(android.R.attr.textColorPrimary),
modifier = Modifier.widthIn(max = 90.dp),
)
Spacer(modifier = Modifier.weight(1f))
@@ -310,6 +303,7 @@ fun ContentScope.OverlayShadeHeader(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
+ val chipHighlight = viewModel.notificationsChipHighlight
if (isShadeLayoutWide) {
Clock(
scale = 1f,
@@ -319,13 +313,13 @@ fun ContentScope.OverlayShadeHeader(
Spacer(modifier = Modifier.width(5.dp))
}
NotificationsChip(
- chipHighlight = viewModel.notificationsChipHighlight,
onClick = viewModel::onNotificationIconChipClicked,
+ backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme),
) {
VariableDayDate(
longerDateText = viewModel.longerDateText,
shorterDateText = viewModel.shorterDateText,
- chipHighlight = viewModel.notificationsChipHighlight,
+ textColor = chipHighlight.foregroundColor(MaterialTheme.colorScheme),
)
}
}
@@ -338,14 +332,13 @@ fun ContentScope.OverlayShadeHeader(
) {
val chipHighlight = viewModel.quickSettingsChipHighlight
SystemIconChip(
- chipHighlight = chipHighlight,
+ backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme),
onClick = viewModel::onSystemIconChipClicked,
) {
StatusIcons(
viewModel = viewModel,
useExpandedFormat = false,
modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
- chipHighlight = chipHighlight,
)
BatteryIcon(
createBatteryMeterViewController =
@@ -515,6 +508,7 @@ private fun BatteryIcon(
batteryIcon.setPercentShowMode(
if (useExpandedFormat) BatteryMeterView.MODE_ESTIMATE else BatteryMeterView.MODE_ON
)
+ // TODO(b/397223606): Get the actual spec for this.
if (chipHighlight is HeaderChipHighlight.Strong) {
batteryIcon.updateColors(primaryColor, inverseColor, inverseColor)
} else if (chipHighlight is HeaderChipHighlight.Weak) {
@@ -553,7 +547,6 @@ private fun ContentScope.StatusIcons(
viewModel: ShadeHeaderViewModel,
useExpandedFormat: Boolean,
modifier: Modifier = Modifier,
- chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
) {
val localContext = LocalContext.current
val themedContext =
@@ -581,6 +574,8 @@ private fun ContentScope.StatusIcons(
viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS)
}
+ val chipHighlight = viewModel.quickSettingsChipHighlight
+
AndroidView(
factory = { context ->
iconManager.setTint(primaryColor, inverseColor)
@@ -617,6 +612,7 @@ private fun ContentScope.StatusIcons(
iconContainer.removeIgnoredSlot(locationSlot)
}
+ // TODO(b/397223606): Get the actual spec for this.
if (chipHighlight is HeaderChipHighlight.Strong) {
iconManager.setTint(inverseColor, primaryColor)
} else if (chipHighlight is HeaderChipHighlight.Weak) {
@@ -629,16 +625,12 @@ private fun ContentScope.StatusIcons(
@Composable
private fun NotificationsChip(
- chipHighlight: HeaderChipHighlight,
onClick: () -> Unit,
modifier: Modifier = Modifier,
+ backgroundColor: Color = Color.Unspecified,
content: @Composable BoxScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
- val chipBackground =
- with(MaterialTheme.colorScheme) {
- if (chipHighlight is HeaderChipHighlight.Strong) chipHighlighted else chipBackground
- }
Box(
modifier =
modifier
@@ -647,7 +639,7 @@ private fun NotificationsChip(
indication = null,
onClick = onClick,
)
- .background(chipBackground, RoundedCornerShape(25.dp))
+ .background(backgroundColor, RoundedCornerShape(25.dp))
.padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical)
) {
content()
@@ -657,7 +649,7 @@ private fun NotificationsChip(
@Composable
private fun SystemIconChip(
modifier: Modifier = Modifier,
- chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
+ backgroundColor: Color = Color.Unspecified,
onClick: (() -> Unit)? = null,
content: @Composable RowScope.() -> Unit,
) {
@@ -667,16 +659,12 @@ private fun SystemIconChip(
with(MaterialTheme.colorScheme) {
Modifier.background(onScrimDim, RoundedCornerShape(CollapsedHeight / 4))
}
- val backgroundColor =
- with(MaterialTheme.colorScheme) {
- if (chipHighlight is HeaderChipHighlight.Strong) chipHighlighted else chipBackground
- }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
modifier
- .thenIf(chipHighlight !is HeaderChipHighlight.None) {
+ .thenIf(backgroundColor != Color.Unspecified) {
Modifier.background(backgroundColor, RoundedCornerShape(25.dp))
.padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
index 64aada52626b..8fbd0519cbdf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
@@ -4,22 +4,16 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
-import com.android.compose.theme.colorAttr
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
@Composable
fun VariableDayDate(
longerDateText: String,
shorterDateText: String,
- chipHighlight: HeaderChipHighlight,
+ textColor: Color,
modifier: Modifier = Modifier,
) {
- val textColor =
- if (chipHighlight is HeaderChipHighlight.Strong)
- colorAttr(android.R.attr.textColorPrimaryInverse)
- else colorAttr(android.R.attr.textColorPrimary)
-
Layout(
contents =
listOf(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index c42e25b20e0d..d046ad114fa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -103,7 +103,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() {
fun testUpdateEmergencyButton() {
Mockito.`when`(telecomManager.isInCall).thenReturn(true)
Mockito.`when`(lockPatternUtils.isSecure(anyInt())).thenReturn(true)
- Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))
+ Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING))
.thenReturn(true)
underTest.updateEmergencyCallButton()
backgroundExecutor.runAllReady()
@@ -112,7 +112,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() {
/* isInCall= */ any(),
/* hasTelephonyRadio= */ any(),
/* simLocked= */ any(),
- /* isSecure= */ any()
+ /* isSecure= */ any(),
)
mainExecutor.runAllReady()
verify(emergencyButton)
@@ -120,7 +120,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() {
/* isInCall= */ eq(true),
/* hasTelephonyRadio= */ eq(true),
/* simLocked= */ any(),
- /* isSecure= */ eq(true)
+ /* isSecure= */ eq(true),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
index 4e14fec8408f..943ada9346e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
@@ -18,6 +18,9 @@ package com.android.systemui.media.controls.ui.binder
import android.animation.Animator
import android.animation.ObjectAnimator
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
import android.testing.TestableLooper
import android.view.View
import android.widget.SeekBar
@@ -30,6 +33,7 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -61,11 +65,11 @@ class SeekBarObserverTest : SysuiTestCase() {
fun setUp() {
context.orCreateTestableResources.addOverride(
R.dimen.qs_media_enabled_seekbar_height,
- enabledHeight
+ enabledHeight,
)
context.orCreateTestableResources.addOverride(
R.dimen.qs_media_disabled_seekbar_height,
- disabledHeight
+ disabledHeight,
)
seekBarView = SeekBar(context)
@@ -110,14 +114,31 @@ class SeekBarObserverTest : SysuiTestCase() {
@Test
fun seekBarProgress() {
+ val elapsedTime = 3000
+ val duration = (1.5 * 60 * 60 * 1000).toInt()
// WHEN part of the track has been played
- val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
+ val data = SeekBarViewModel.Progress(true, true, true, false, elapsedTime, duration, true)
observer.onChanged(data)
// THEN seek bar shows the progress
- assertThat(seekBarView.progress).isEqualTo(3000)
- assertThat(seekBarView.max).isEqualTo(120000)
-
- val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
+ assertThat(seekBarView.progress).isEqualTo(elapsedTime)
+ assertThat(seekBarView.max).isEqualTo(duration)
+
+ val expectedProgress =
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(Measure(3, MeasureUnit.SECOND))
+ val expectedDuration =
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(
+ Measure(1, MeasureUnit.HOUR),
+ Measure(30, MeasureUnit.MINUTE),
+ Measure(0, MeasureUnit.SECOND),
+ )
+ val desc =
+ context.getString(
+ R.string.controls_media_seekbar_description,
+ expectedProgress,
+ expectedDuration,
+ )
assertThat(seekBarView.contentDescription).isEqualTo(desc)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
index e38ea30daf0e..9fd189f35c32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
@@ -20,11 +20,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
@@ -33,22 +36,20 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class CallChipInteractorTest : SysuiTestCase() {
- val kosmos = Kosmos()
+ val kosmos = testKosmos().useUnconfinedTestDispatcher()
val repo = kosmos.ongoingCallRepository
val underTest = kosmos.callChipInteractor
@Test
- fun ongoingCallState_matchesRepo() =
- kosmos.testScope.runTest {
+ fun ongoingCallState_matchesState() =
+ kosmos.runTest {
val latest by collectLastValue(underTest.ongoingCallState)
- val inCall = inCallModel(startTimeMs = 1000)
- repo.setOngoingCallState(inCall)
- assertThat(latest).isEqualTo(inCall)
+ addOngoingCallState(key = "testKey")
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
- val noCall = OngoingCallModel.NoCall
- repo.setOngoingCallState(noCall)
- assertThat(latest).isEqualTo(noCall)
+ removeOngoingCallState(key = "testKey")
+ assertThat(latest).isEqualTo(OngoingCallModel.NoCall)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index e044d1db92a9..fda4ab005446 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -27,7 +27,9 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -35,17 +37,11 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
-import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
-import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -60,10 +56,7 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class CallChipViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val notificationListRepository = kosmos.activeNotificationListRepository
- private val testScope = kosmos.testScope
- private val repo = kosmos.ongoingCallRepository
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val chipBackgroundView = mock<ChipBackgroundContainer>()
private val chipView =
@@ -82,53 +75,53 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
fun chip_noCall_isHidden() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.NoCall)
+ removeOngoingCallState("testKey")
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
}
@Test
fun chip_inCall_zeroStartTime_isShownAsIconOnly() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 0))
+ addOngoingCallState(startTimeMs = 0)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
@Test
fun chip_inCall_negativeStartTime_isShownAsIconOnly() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = -2))
+ addOngoingCallState(startTimeMs = -2)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
@Test
fun chip_inCall_positiveStartTime_isShownAsTimer() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 345))
+ addOngoingCallState(startTimeMs = 345)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
}
@Test
fun chip_inCall_startTimeConvertedToElapsedRealtime() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
kosmos.fakeSystemClock.setElapsedRealtime(400_000)
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
+ addOngoingCallState(startTimeMs = 1000)
// The OngoingCallModel start time is relative to currentTimeMillis, so this call
// started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be
@@ -141,13 +134,11 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_positiveStartTime_connectedDisplaysFlagOn_iconIsNotifIcon() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val notifKey = "testNotifKey"
- repo.setOngoingCallState(
- inCallModel(startTimeMs = 1000, notificationIcon = null, notificationKey = notifKey)
- )
+ addOngoingCallState(startTimeMs = 1000, statusBarChipIconView = null, key = notifKey)
assertThat((latest as OngoingActivityChipModel.Active).icon)
.isInstanceOf(
@@ -163,16 +154,14 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon_withContentDescription() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val notifIcon = createStatusBarIconViewOrNull()
- repo.setOngoingCallState(
- inCallModel(
- startTimeMs = 0,
- notificationIcon = notifIcon,
- appName = "Fake app name",
- )
+ addOngoingCallState(
+ startTimeMs = 0,
+ statusBarChipIconView = notifIcon,
+ appName = "Fake app name",
)
assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -190,16 +179,13 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(
- inCallModel(
- startTimeMs = 0,
- notificationIcon = createStatusBarIconViewOrNull(),
- notificationKey = "notifKey",
- appName = "Fake app name",
- )
+ addOngoingCallState(
+ key = "notifKey",
+ statusBarChipIconView = createStatusBarIconViewOrNull(),
+ appName = "Fake app name",
)
assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -219,10 +205,10 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOff_iconIsPhone() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null))
+ addOngoingCallState(statusBarChipIconView = null)
assertThat((latest as OngoingActivityChipModel.Active).icon)
.isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
@@ -237,16 +223,13 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(
- inCallModel(
- startTimeMs = 1000,
- notificationIcon = null,
- notificationKey = "notifKey",
- appName = "Fake app name",
- )
+ addOngoingCallState(
+ key = "notifKey",
+ statusBarChipIconView = null,
+ appName = "Fake app name",
)
assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -265,10 +248,10 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
fun chip_positiveStartTime_colorsAreAccentThemed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null))
+ addOngoingCallState(startTimeMs = 1000, promotedContent = null)
assertThat((latest as OngoingActivityChipModel.Active).colors)
.isEqualTo(ColorsModel.AccentThemed)
@@ -276,10 +259,10 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
fun chip_zeroStartTime_colorsAreAccentThemed() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null))
+ addOngoingCallState(startTimeMs = 0, promotedContent = null)
assertThat((latest as OngoingActivityChipModel.Active).colors)
.isEqualTo(ColorsModel.AccentThemed)
@@ -287,19 +270,19 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
fun chip_resetsCorrectly() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
kosmos.fakeSystemClock.setElapsedRealtime(400_000)
// Start a call
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
+ addOngoingCallState(key = "testKey", startTimeMs = 1000)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java)
assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs)
.isEqualTo(398_000)
// End the call
- repo.setOngoingCallState(OngoingCallModel.NoCall)
+ removeOngoingCallState(key = "testKey")
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
// Let 100_000ms elapse
@@ -307,7 +290,7 @@ class CallChipViewModelTest : SysuiTestCase() {
kosmos.fakeSystemClock.setElapsedRealtime(500_000)
// Start a new call, which started 1000ms ago
- repo.setOngoingCallState(inCallModel(startTimeMs = 102_000))
+ addOngoingCallState(key = "testKey", startTimeMs = 102_000)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java)
assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs)
.isEqualTo(499_000)
@@ -316,10 +299,10 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@DisableChipsModernization
fun chip_inCall_nullIntent_nullClickListener() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null))
+ addOngoingCallState(contentIntent = null)
assertThat((latest as OngoingActivityChipModel.Active).onClickListenerLegacy).isNull()
}
@@ -327,11 +310,11 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@DisableChipsModernization
fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
- repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = pendingIntent))
+ addOngoingCallState(startTimeMs = 1000, contentIntent = pendingIntent)
val clickListener = (latest as OngoingActivityChipModel.Active).onClickListenerLegacy
assertThat(clickListener).isNotNull()
@@ -345,11 +328,11 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@DisableChipsModernization
fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
- repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = pendingIntent))
+ addOngoingCallState(startTimeMs = 0, contentIntent = pendingIntent)
val clickListener = (latest as OngoingActivityChipModel.Active).onClickListenerLegacy
assertThat(clickListener).isNotNull()
@@ -364,14 +347,10 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableChipsModernization
fun chip_inCall_nullIntent_noneClickBehavior() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- postOngoingCallNotification(
- repository = notificationListRepository,
- startTimeMs = 1000L,
- intent = null,
- )
+ addOngoingCallState(startTimeMs = 1000, contentIntent = null)
assertThat((latest as OngoingActivityChipModel.Active).clickBehavior)
.isInstanceOf(OngoingActivityChipModel.ClickBehavior.None::class.java)
@@ -380,15 +359,11 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableChipsModernization
fun chip_inCall_positiveStartTime_validIntent_clickBehaviorLaunchesIntent() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
- postOngoingCallNotification(
- repository = notificationListRepository,
- startTimeMs = 1000L,
- intent = pendingIntent,
- )
+ addOngoingCallState(startTimeMs = 1000, contentIntent = pendingIntent)
val clickBehavior = (latest as OngoingActivityChipModel.Active).clickBehavior
assertThat(clickBehavior)
@@ -405,15 +380,11 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableChipsModernization
fun chip_inCall_zeroStartTime_validIntent_clickBehaviorLaunchesIntent() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chip)
val pendingIntent = mock<PendingIntent>()
- postOngoingCallNotification(
- repository = notificationListRepository,
- startTimeMs = 0L,
- intent = pendingIntent,
- )
+ addOngoingCallState(startTimeMs = 0, contentIntent = pendingIntent)
val clickBehavior = (latest as OngoingActivityChipModel.Active).clickBehavior
assertThat(clickBehavior)
@@ -435,27 +406,6 @@ class CallChipViewModelTest : SysuiTestCase() {
mock<StatusBarIconView>()
}
- fun postOngoingCallNotification(
- repository: ActiveNotificationListRepository,
- startTimeMs: Long,
- intent: PendingIntent?,
- ) {
- repository.activeNotifications.value =
- ActiveNotificationsStore.Builder()
- .apply {
- addIndividualNotif(
- activeNotificationModel(
- key = "notif1",
- whenTime = startTimeMs,
- callType = CallType.Ongoing,
- statusBarChipIcon = null,
- contentIntent = intent,
- )
- )
- }
- .build()
- }
-
private val PROMOTED_CONTENT_WITH_COLOR =
PromotedNotificationContentModel.Builder("notif")
.apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index ab475c5edb76..2f6bedb42e45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -37,6 +37,8 @@ import com.android.systemui.statusbar.layout.LetterboxAppearance
import com.android.systemui.statusbar.layout.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.layout.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
+import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -387,6 +389,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @DisableChipsModernization
fun statusBarMode_ongoingCallAndFullscreen_semiTransparent() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
@@ -398,6 +401,19 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @EnableChipsModernization
+ fun statusBarMode_ongoingProcessRequiresStatusBarVisible_andFullscreen_semiTransparent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ underTest.setOngoingProcessRequiresStatusBarVisible(true)
+ onSystemBarAttributesChanged(requestedVisibleTypes = WindowInsets.Type.navigationBars())
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+ }
+
+ @Test
+ @DisableChipsModernization
fun statusBarMode_ongoingCallButNotFullscreen_matchesAppearance() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
@@ -413,6 +429,23 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @EnableChipsModernization
+ fun statusBarMode_ongoingProcessRequiresStatusBarVisible_butNotFullscreen_matchesAppearance() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ underTest.setOngoingProcessRequiresStatusBarVisible(true)
+
+ onSystemBarAttributesChanged(
+ requestedVisibleTypes = WindowInsets.Type.statusBars(),
+ appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+ }
+
+ @Test
+ @DisableChipsModernization
fun statusBarMode_fullscreenButNotOngoingCall_matchesAppearance() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
@@ -427,6 +460,21 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @EnableChipsModernization
+ fun statusBarMode_fullscreen_butNotOngoingProcessRequiresStatusBarVisible_matchesAppearance() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.statusBarAppearance)
+
+ underTest.setOngoingProcessRequiresStatusBarVisible(false)
+ onSystemBarAttributesChanged(
+ requestedVisibleTypes = WindowInsets.Type.navigationBars(),
+ appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+ )
+
+ assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+ }
+
+ @Test
fun statusBarMode_transientShown_semiTransparent() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
index 426af264da07..83e26c4220b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection
+import android.app.Notification
+import android.graphics.Color
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.TestableLooper.RunWithLooper
@@ -72,4 +74,35 @@ class BundleEntryTest : SysuiTestCase() {
fun getKey_adapter() {
assertThat(underTest.entryAdapter.key).isEqualTo("key")
}
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isClearable_adapter() {
+ assertThat(underTest.entryAdapter.isClearable).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getSummarization_adapter() {
+ assertThat(underTest.entryAdapter.summarization).isNull()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getContrastedColor_adapter() {
+ assertThat(underTest.entryAdapter.getContrastedColor(context, false, Color.WHITE))
+ .isEqualTo(Notification.COLOR_DEFAULT)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun canPeek_adapter() {
+ assertThat(underTest.entryAdapter.canPeek()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getWhen_adapter() {
+ assertThat(underTest.entryAdapter.`when`).isEqualTo(0)
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 19d1224a9bf3..1f5c6722f38e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -163,9 +163,10 @@ public class NotificationEntryTest extends SysuiTestCase {
@Test
public void testIsExemptFromDndVisualSuppression_media() {
+ MediaSession session = new MediaSession(mContext, "test");
Notification.Builder n = new Notification.Builder(mContext, "")
.setStyle(new Notification.MediaStyle()
- .setMediaSession(mock(MediaSession.Token.class)))
+ .setMediaSession(session.getSessionToken()))
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
@@ -593,6 +594,76 @@ public class NotificationEntryTest extends SysuiTestCase {
assertThat(entry.getEntryAdapter().getGroupRoot()).isEqualTo(parent.getEntryAdapter());
}
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void isClearable_adapter() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ entry.setRow(row);
+
+ assertThat(entry.getEntryAdapter().isClearable()).isEqualTo(entry.isClearable());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getSummarization_adapter() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ Ranking ranking = new RankingBuilder(entry.getRanking())
+ .setSummarization("hello")
+ .build();
+ entry.setRanking(ranking);
+ entry.setRow(row);
+
+ assertThat(entry.getEntryAdapter().getSummarization()).isEqualTo("hello");
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getIcons_adapter() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setChannel(mChannel)
+ .setId(mId++)
+ .setNotification(notification)
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ entry.setRow(row);
+
+ assertThat(entry.getEntryAdapter().getIcons()).isEqualTo(entry.getIcons());
+ }
+
private Notification.Action createContextualAction(String title) {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 7781df1ad91f..43cb9575b609 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
+import android.app.Notification.MediaStyle
+import android.media.session.MediaSession
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
+import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.dumpManager
@@ -36,6 +39,7 @@ import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -217,6 +221,16 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
mock<ExpandableNotificationRow>().apply {
whenever(isMediaRow).thenReturn(true)
}
+ sbn = SbnBuilder().setNotification(
+ Notification.Builder(context, "channel").setStyle(
+ MediaStyle().setMediaSession(
+ MediaSession(
+ context,
+ "tag"
+ ).sessionToken
+ )
+ ).build()
+ ).build()
}
collectionListener.onEntryAdded(fakeEntry)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index 4c1f4f17e00c..1b8d64d5483c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -80,9 +80,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.testKosmos
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.wmshell.BubblesManager
-import java.util.Optional
-import kotlin.test.assertNotNull
-import kotlin.test.fail
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
@@ -110,6 +107,9 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+import java.util.Optional
+import kotlin.test.assertNotNull
+import kotlin.test.fail
/** Tests for [NotificationGutsManager]. */
@SmallTest
@@ -509,7 +509,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
- whenever(row.isNonblockable).thenReturn(false)
whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -546,7 +545,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- whenever(row.isNonblockable).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
@@ -586,7 +584,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- whenever(row.isNonblockable).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
@@ -641,7 +638,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
): NotificationMenuRowPlugin.MenuItem {
val menuRow: NotificationMenuRowPlugin =
NotificationMenuRow(mContext, peopleNotificationIdentifier)
- menuRow.createMenu(row, row.entry.sbn)
+ menuRow.createMenu(row)
val menuItem = menuRow.getLongpressMenuItem(mContext)
assertNotNull(menuItem)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 027e899e20df..9fdfca14a5b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -72,7 +72,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
public void testAttachDetach() {
NotificationMenuRowPlugin row =
new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewUtils.attachView(row.getMenuView());
TestableLooper.get(this).processAllMessages();
ViewUtils.detachView(row.getMenuView());
@@ -83,9 +83,9 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
public void testRecreateMenu() {
NotificationMenuRowPlugin row =
new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
assertTrue(row.getMenuView() != null);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
assertTrue(row.getMenuView() != null);
}
@@ -103,7 +103,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewGroup container = (ViewGroup) row.getMenuView();
// noti blocking
@@ -116,7 +116,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewGroup container = (ViewGroup) row.getMenuView();
// just for noti blocking
@@ -129,7 +129,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewGroup container = (ViewGroup) row.getMenuView();
// one for snooze and one for noti blocking
@@ -142,7 +142,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 1);
NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
ViewGroup container = (ViewGroup) row.getMenuView();
// Clear menu
@@ -417,7 +417,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
public void testOnTouchMove() {
NotificationMenuRow row = Mockito.spy(
new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
- row.createMenu(mRow, null);
+ row.createMenu(mRow);
doReturn(50f).when(row).getDismissThreshold();
doReturn(true).when(row).canBeDismissed();
doReturn(mView).when(row).getMenuView();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
index e5cb0fbc9e4b..885e71e7a7fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.testKosmos
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,7 +50,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
private val sectionsManager = mock<NotificationSectionsManager>()
private val msdlPlayer = kosmos.fakeMSDLPlayer
private var canRowBeDismissed = true
- private var magneticAnimationsCancelled = false
+ private var magneticAnimationsCancelled = MutableList(childrenNumber) { false }
private val underTest = kosmos.magneticNotificationRowManagerImpl
@@ -64,8 +65,10 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
NotificationTestHelper(mContext, mDependency, kosmos.testableLooper, featureFlags)
children = notificationTestHelper.createGroup(childrenNumber).childrenContainer
swipedRow = children.attachedChildren[childrenNumber / 2]
- configureMagneticRowListener(swipedRow)
- magneticAnimationsCancelled = false
+ children.attachedChildren.forEachIndexed { index, row ->
+ row.magneticRowListener = row.magneticRowListener.asTestableListener(index)
+ }
+ magneticAnimationsCancelled.replaceAll { false }
}
@Test
@@ -259,14 +262,14 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
underTest.onMagneticInteractionEnd(swipedRow, velocity = null)
// THEN magnetic animations are cancelled
- assertThat(magneticAnimationsCancelled).isTrue()
+ assertThat(magneticAnimationsCancelled[childrenNumber / 2]).isTrue()
}
@Test
fun onMagneticInteractionEnd_forMagneticNeighbor_cancelsMagneticAnimations() =
kosmos.testScope.runTest {
- val neighborRow = children.attachedChildren[childrenNumber / 2 - 1]
- configureMagneticRowListener(neighborRow)
+ val neighborIndex = childrenNumber / 2 - 1
+ val neighborRow = children.attachedChildren[neighborIndex]
// GIVEN that targets are set
setTargets()
@@ -275,9 +278,15 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
underTest.onMagneticInteractionEnd(neighborRow, null)
// THEN magnetic animations are cancelled
- assertThat(magneticAnimationsCancelled).isTrue()
+ assertThat(magneticAnimationsCancelled[neighborIndex]).isTrue()
}
+ @After
+ fun tearDown() {
+ // We reset the manager so that all MagneticRowListener can cancel all animations
+ underTest.reset()
+ }
+
private fun setDetachedState() {
val threshold = 100f
underTest.setSwipeThresholdPx(threshold)
@@ -302,27 +311,33 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
originalTranslation *
MagneticNotificationRowManagerImpl.MAGNETIC_REDUCTION
- private fun configureMagneticRowListener(row: ExpandableNotificationRow) {
- val listener =
- object : MagneticRowListener {
- override fun setMagneticTranslation(translation: Float) {
- row.translation = translation
- }
+ private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener {
+ val delegate = this
+ return object : MagneticRowListener {
+ override fun setMagneticTranslation(translation: Float) {
+ delegate.setMagneticTranslation(translation)
+ }
- override fun triggerMagneticForce(
- endTranslation: Float,
- springForce: SpringForce,
- startVelocity: Float,
- ) {}
+ override fun triggerMagneticForce(
+ endTranslation: Float,
+ springForce: SpringForce,
+ startVelocity: Float,
+ ) {
+ delegate.triggerMagneticForce(endTranslation, springForce, startVelocity)
+ }
- override fun cancelMagneticAnimations() {
- magneticAnimationsCancelled = true
- }
+ override fun cancelMagneticAnimations() {
+ magneticAnimationsCancelled[rowIndex] = true
+ delegate.cancelMagneticAnimations()
+ }
- override fun cancelTranslationAnimations() {}
+ override fun cancelTranslationAnimations() {
+ delegate.cancelTranslationAnimations()
+ }
- override fun canRowBeDismissed(): Boolean = canRowBeDismissed
+ override fun canRowBeDismissed(): Boolean {
+ return canRowBeDismissed
}
- row.magneticRowListener = listener
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 01ba4df3a314..7603eecd27ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -146,6 +146,20 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ fun resetViewStates_defaultHun_hasShadow() {
+ val headsUpTop = 200f
+ ambientState.headsUpTop = headsUpTop
+
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ assertThat(notificationRow.viewState.zTranslation).isGreaterThan(baseZ)
+ }
+
+ @Test
@DisableSceneContainer
fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index d17abd7da05c..dbfd57fd0f0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.os.ParcelUuid
+import android.platform.test.annotations.EnableFlags
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
@@ -33,6 +34,8 @@ import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
@@ -897,6 +900,43 @@ class MobileIconsInteractorTest : SysuiTestCase() {
assertThat(latest).isEqualTo(2)
}
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_tracksNumberOfSubscriptions() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isStackable)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1))
+ assertThat(latest).isFalse()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ assertThat(latest).isTrue()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2, SUB_3_OPP))
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_checksForTerrestrialConnections() =
+ kosmos.runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ val latest by collectLastValue(underTest.isStackable)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ assertThat(latest).isTrue()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, exclusivelyNonTerrestrialSub))
+ assertThat(latest).isFalse()
+ }
+
/**
* Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
* flow.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
new file mode 100644
index 000000000000..20bdebd069f7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StackedMobileIconViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+
+ private val Kosmos.underTest: StackedMobileIconViewModel by Fixture {
+ stackedMobileIconViewModel
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
+ kosmos.underTest.activateIn(testScope)
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_filtersOutNonDualConnections() =
+ kosmos.runTest {
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf()
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ assertThat(underTest.dualSim).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_filtersOutNonCellularIcons() =
+ kosmos.runTest {
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor
+ .getInteractorForSubId(SUB_1.subscriptionId)!!
+ .signalLevelIcon
+ .value =
+ SignalIconModel.Satellite(
+ level = 0,
+ icon = Icon.Resource(res = 0, contentDescription = null),
+ )
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ assertThat(underTest.dualSim).isNull()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_tracksActiveSubId() =
+ kosmos.runTest {
+ // Active sub id is null, order is unchanged
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ setIconLevel(SUB_1.subscriptionId, 1)
+ setIconLevel(SUB_2.subscriptionId, 2)
+
+ assertThat(underTest.dualSim!!.primary.level).isEqualTo(1)
+ assertThat(underTest.dualSim!!.secondary.level).isEqualTo(2)
+
+ // Active sub is 2, order is swapped
+ fakeMobileIconsInteractor.activeMobileDataSubscriptionId.value = SUB_2.subscriptionId
+
+ assertThat(underTest.dualSim!!.primary.level).isEqualTo(2)
+ assertThat(underTest.dualSim!!.secondary.level).isEqualTo(1)
+ }
+
+ private fun setIconLevel(subId: Int, level: Int) {
+ with(kosmos.fakeMobileIconsInteractor.getInteractorForSubId(subId)!!) {
+ signalLevelIcon.value =
+ (signalLevelIcon.value as SignalIconModel.Cellular).copy(level = level)
+ }
+ }
+
+ companion object {
+ private val SUB_1 =
+ SubscriptionModel(
+ subscriptionId = 1,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ private val SUB_2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = false,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ private val SUB_3 =
+ SubscriptionModel(
+ subscriptionId = 3,
+ isOpportunistic = false,
+ carrierName = "Carrier 3",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index eb961bd5f4ae..f91e3a612862 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -64,6 +64,8 @@ class FakeHomeStatusBarViewModel(
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
+ override val canShowOngoingActivityChips: Flow<Boolean> = MutableStateFlow(false)
+
override val batteryViewModelFactory: BatteryViewModel.Factory =
object : BatteryViewModel.Factory {
override fun create(): BatteryViewModel = mock(BatteryViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index 51484baf1b7b..8a796fc33608 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -678,6 +678,60 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
+ fun canShowOngoingActivityChips_statusBarHidden_noSecureCamera_noHun_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ // home status bar not allowed
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun canShowOngoingActivityChips_statusBarNotHidden_secureCamera_noHun_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope = testScope,
+ )
+ kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hun_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
fun isClockVisible_allowedByDisableFlags_visible() =
kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index d3218ad8c9fb..a0bf7f00b11c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -203,6 +203,8 @@ public interface QS extends FragmentBase {
*/
void setIsNotificationPanelFullWidth(boolean isFullWidth);
+ default void setQSExpandingOrCollapsing(boolean isQSExpandingOrCollapsing) {}
+
/**
* Callback for when QSPanel container is scrolled
*/
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index 94fdbae83253..9b961d2535ae 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -122,7 +122,7 @@ public interface NotificationMenuRowPlugin extends Plugin {
public void setAppName(String appName);
- public void createMenu(ViewGroup parent, StatusBarNotification sbn);
+ public void createMenu(ViewGroup parent);
public void resetMenu();
@@ -215,9 +215,8 @@ public interface NotificationMenuRowPlugin extends Plugin {
/**
* Callback used to signal the menu that its parent notification has been updated.
- * @param sbn
*/
- public void onNotificationUpdated(StatusBarNotification sbn);
+ public void onNotificationUpdated();
/**
* Callback used to signal the menu that a user is moving the parent notification.
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3fc46ed6c9d1..359bd2bcb37c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3170,8 +3170,8 @@
<string name="controls_media_settings_button">Settings</string>
<!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
<string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
- <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
- <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
+ <!-- Content description for media controls progress bar [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1 hour 2 minutes 30 seconds">%1$s</xliff:g> of <xliff:g id="total_time" example="4 hours 5 seconds">%2$s</xliff:g></string>
<!-- Placeholder title to inform user that an app has posted media controls [CHAR_LIMIT=NONE] -->
<string name="controls_media_empty_title"><xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g> is running</string>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 0f1da509468a..ae3a76e2d2ca 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -71,6 +71,7 @@ android_library {
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:msdl",
"//frameworks/libs/systemui:view_capture",
+ "am_flags_lib",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml
index f22dac4b9fb4..98e5cea0e78f 100644
--- a/packages/SystemUI/shared/res/values/bools.xml
+++ b/packages/SystemUI/shared/res/values/bools.xml
@@ -22,4 +22,7 @@
<resources>
<!-- Whether to add padding at the bottom of the complication clock -->
<bool name="dream_overlay_complication_clock_bottom_padding">false</bool>
-</resources> \ No newline at end of file
+
+ <!-- Whether to mark tasks that are present in the UI as perceptible tasks. -->
+ <bool name="config_usePerceptibleTasks">false</bool>
+</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index ed9ba7aa455b..487d1ce2514e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -46,6 +46,8 @@ import android.view.Display;
import android.window.TaskSnapshot;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.server.am.Flags;
+import com.android.systemui.shared.R;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -227,6 +229,17 @@ public class ActivityManagerWrapper {
}
/**
+ * Sets whether or not the specified task is perceptible.
+ */
+ public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) {
+ try {
+ return getService().setTaskIsPerceptible(taskId, isPerceptible);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Removes a task by id.
*/
public void removeTask(final int taskId) {
@@ -311,10 +324,23 @@ public class ActivityManagerWrapper {
}
/**
+ * Returns true if tasks with a presence in the UI should be marked as perceptible tasks.
+ */
+ public static boolean usePerceptibleTasks(Context context) {
+ return Flags.perceptibleTasks()
+ && context.getResources().getBoolean(R.bool.config_usePerceptibleTasks);
+ }
+
+ /**
* Returns true if the running task represents the home task
*/
public static boolean isHomeTask(RunningTaskInfo info) {
return info.configuration.windowConfiguration.getActivityType()
== WindowConfiguration.ACTIVITY_TYPE_HOME;
}
+
+ public boolean isRunningInTestHarness() {
+ return ActivityManager.isRunningInTestHarness()
+ || ActivityManager.isRunningInUserTestHarness();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index 5e36539ecbec..a7bb11ea0442 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -156,7 +156,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
mMainExecutor.execute(() -> mView.updateEmergencyCallButton(
/* isInCall= */ isInCall,
/* hasTelephonyRadio= */ getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY),
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING),
/* simLocked= */ mKeyguardUpdateMonitor.isSimPinVoiceSecure(),
/* isSecure= */ isSecure));
});
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
index 7c141c1b561e..5247acc94e03 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
@@ -58,11 +58,22 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
private final BiMap<Integer, AmbientVolumeSlider> mSideToSliderMap = HashBiMap.create();
private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+ private HearingDevicesUiEventLogger mUiEventLogger;
+ private int mLaunchSourceId;
+
private final AmbientVolumeSlider.OnChangeListener mSliderOnChangeListener =
(slider, value) -> {
- if (mListener != null) {
- final int side = mSideToSliderMap.inverse().get(slider);
- mListener.onSliderValueChange(side, value);
+ final Integer side = mSideToSliderMap.inverse().get(slider);
+ if (side != null) {
+ if (mUiEventLogger != null) {
+ HearingDevicesUiEvent uiEvent = side == SIDE_UNIFIED
+ ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED
+ : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED;
+ mUiEventLogger.log(uiEvent, mLaunchSourceId);
+ }
+ if (mListener != null) {
+ mListener.onSliderValueChange(side, value);
+ }
}
};
@@ -94,6 +105,12 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
return;
}
setMuted(!mMuted);
+ if (mUiEventLogger != null) {
+ HearingDevicesUiEvent uiEvent = mMuted
+ ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_MUTE
+ : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_UNMUTE;
+ mUiEventLogger.log(uiEvent, mLaunchSourceId);
+ }
if (mListener != null) {
mListener.onAmbientVolumeIconClick();
}
@@ -103,6 +120,12 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
mExpandIcon = requireViewById(R.id.ambient_expand_icon);
mExpandIcon.setOnClickListener(v -> {
setExpanded(!mExpanded);
+ if (mUiEventLogger != null) {
+ HearingDevicesUiEvent uiEvent = mExpanded
+ ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS
+ : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS;
+ mUiEventLogger.log(uiEvent, mLaunchSourceId);
+ }
if (mListener != null) {
mListener.onExpandIconClick();
}
@@ -243,6 +266,11 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
updateVolumeLevel();
}
+ void setUiEventLogger(HearingDevicesUiEventLogger uiEventLogger, int launchSourceId) {
+ mUiEventLogger = uiEventLogger;
+ mLaunchSourceId = launchSourceId;
+ }
+
private void updateVolumeLevel() {
int leftLevel, rightLevel;
if (mExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 22ecb0af8c18..786d27af3994 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -382,6 +382,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) {
final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout);
+ ambientLayout.setUiEventLogger(mUiEventLogger, mLaunchSourceId);
mAmbientController = new AmbientVolumeUiController(
mDialog.getContext(), mLocalBluetoothManager, ambientLayout);
mAmbientController.setShowUiWhenLocalDataExist(false);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
index 9e77b02be495..fe1d5040c6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
@@ -29,7 +29,17 @@ enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnu
@UiEvent(doc = "Click on the device gear to enter device detail page")
HEARING_DEVICES_GEAR_CLICK(1853),
@UiEvent(doc = "Select a preset from preset spinner") HEARING_DEVICES_PRESET_SELECT(1854),
- @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856);
+ @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856),
+ @UiEvent(doc = "Change the ambient volume with unified control")
+ HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED(2149),
+ @UiEvent(doc = "Change the ambient volume with separated control")
+ HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED(2150),
+ @UiEvent(doc = "Mute the ambient volume") HEARING_DEVICES_AMBIENT_MUTE(2151),
+ @UiEvent(doc = "Unmute the ambient volume") HEARING_DEVICES_AMBIENT_UNMUTE(2152),
+ @UiEvent(doc = "Expand the ambient volume controls")
+ HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS(2153),
+ @UiEvent(doc = "Collapse the ambient volume controls")
+ HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154);
override fun getId(): Int = this.id
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index cce1ae1a2947..6473b1c30586 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -237,7 +237,15 @@ constructor(
with(mediaHost) {
expansion = MediaHostState.EXPANDED
expandedMatchesParentHeight = true
- showsOnlyActiveMedia = false
+ if (v2FlagEnabled()) {
+ // Only show active media to match lock screen, not resumable media, which can
+ // persist
+ // for up to 2 days.
+ showsOnlyActiveMedia = true
+ } else {
+ // Maintain old behavior on tablet until V2 flag rolls out.
+ showsOnlyActiveMedia = false
+ }
falsingProtectionNeeded = false
disablePagination = true
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 6d796d96ea71..3f538203aee9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -49,9 +49,10 @@ import com.android.systemui.media.controls.ui.util.MediaArtworkHelper
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaActionViewModel
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
-import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_CENTER_ALPHA
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_ALL
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_COMPACT
import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel
@@ -537,18 +538,24 @@ object MediaControlViewBinder {
height: Int,
): LayerDrawable {
val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
- val alpha =
+ val startAlpha =
if (Flags.mediaControlsA11yColors()) {
- MEDIA_PLAYER_SCRIM_CENTER_ALPHA
- } else {
MEDIA_PLAYER_SCRIM_START_ALPHA
+ } else {
+ MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY
+ }
+ val endAlpha =
+ if (Flags.mediaControlsA11yColors()) {
+ MEDIA_PLAYER_SCRIM_END_ALPHA
+ } else {
+ MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY
}
return MediaArtworkHelper.setUpGradientColorOnDrawable(
albumArt,
context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
mutableColorScheme,
- alpha,
- MEDIA_PLAYER_SCRIM_END_ALPHA,
+ startAlpha,
+ endAlpha,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
index 34f7c4dcaec0..c9716be52408 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
@@ -18,6 +18,9 @@ package com.android.systemui.media.controls.ui.binder
import android.animation.Animator
import android.animation.ObjectAnimator
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
import android.text.format.DateUtils
import androidx.annotation.UiThread
import androidx.lifecycle.Observer
@@ -28,8 +31,11 @@ import com.android.systemui.media.controls.ui.drawable.SquigglyProgress
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
+import java.util.Locale
private const val TAG = "SeekBarObserver"
+private const val MIN_IN_SEC = 60
+private const val HOUR_IN_SEC = MIN_IN_SEC * 60
/**
* Observer for changes from SeekBarViewModel.
@@ -127,10 +133,9 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
}
holder.seekBar.setMax(data.duration)
- val totalTimeString =
- DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS)
+ val totalTimeDescription = formatTimeContentDescription(data.duration)
if (data.scrubbing) {
- holder.scrubbingTotalTimeView.text = totalTimeString
+ holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration)
}
data.elapsedTime?.let {
@@ -148,20 +153,62 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
}
}
- val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS)
+ val elapsedTimeDescription = formatTimeContentDescription(it)
if (data.scrubbing) {
- holder.scrubbingElapsedTimeView.text = elapsedTimeString
+ holder.scrubbingElapsedTimeView.text = formatTimeLabel(it)
}
holder.seekBar.contentDescription =
holder.seekBar.context.getString(
R.string.controls_media_seekbar_description,
- elapsedTimeString,
- totalTimeString
+ elapsedTimeDescription,
+ totalTimeDescription,
)
}
}
+ /** Returns a time string suitable for display, e.g. "12:34" */
+ private fun formatTimeLabel(milliseconds: Int): CharSequence {
+ return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS)
+ }
+
+ /**
+ * Returns a time string suitable for content description, e.g. "12 minutes 34 seconds"
+ *
+ * Follows same logic as Chronometer#formatDuration
+ */
+ private fun formatTimeContentDescription(milliseconds: Int): CharSequence {
+ var seconds = milliseconds / DateUtils.SECOND_IN_MILLIS
+
+ val hours =
+ if (seconds >= HOUR_IN_SEC) {
+ seconds / HOUR_IN_SEC
+ } else {
+ 0
+ }
+ seconds -= hours * HOUR_IN_SEC
+
+ val minutes =
+ if (seconds >= MIN_IN_SEC) {
+ seconds / MIN_IN_SEC
+ } else {
+ 0
+ }
+ seconds -= minutes * MIN_IN_SEC
+
+ val measures = arrayListOf<Measure>()
+ if (hours > 0) {
+ measures.add(Measure(hours, MeasureUnit.HOUR))
+ }
+ if (minutes > 0) {
+ measures.add(Measure(minutes, MeasureUnit.MINUTE))
+ }
+ measures.add(Measure(seconds, MeasureUnit.SECOND))
+
+ return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(*measures.toTypedArray())
+ }
+
@VisibleForTesting
open fun buildResetAnimator(targetTime: Int): Animator {
val animator =
@@ -169,7 +216,7 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
holder.seekBar,
"progress",
holder.seekBar.progress,
- targetTime + RESET_ANIMATION_DURATION_MS
+ targetTime + RESET_ANIMATION_DURATION_MS,
)
animator.setAutoCancel(true)
animator.duration = RESET_ANIMATION_DURATION_MS.toLong()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 39c08daf53d6..694a4c7e493d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -23,7 +23,10 @@ import static com.android.systemui.Flags.communalHub;
import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation;
import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions;
import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
-import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_CENTER_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -176,9 +179,7 @@ public class MediaControlPanel {
protected static final int SMARTSPACE_CARD_DISMISS_EVENT = 761;
private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
- private static final float MEDIA_SCRIM_START_ALPHA = 0.25f;
private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
- private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f;
private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
@@ -1093,11 +1094,12 @@ public class MediaControlPanel {
Drawable albumArt = getScaledBackground(artworkIcon, width, height);
GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
R.drawable.qs_media_scrim).mutate();
- float startAlpha = (Flags.mediaControlsA11yColors())
- ? MEDIA_PLAYER_SCRIM_CENTER_ALPHA
- : MEDIA_SCRIM_START_ALPHA;
+ if (Flags.mediaControlsA11yColors()) {
+ return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
+ MEDIA_PLAYER_SCRIM_START_ALPHA, MEDIA_PLAYER_SCRIM_END_ALPHA);
+ }
return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
- startAlpha, MEDIA_PLAYER_SCRIM_END_ALPHA);
+ MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY, MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY);
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 198155b3f297..b687dce20b06 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -1137,6 +1137,7 @@ constructor(
) {
gutsViewHolder.gutsText.setTypeface(menuTF)
gutsViewHolder.dismissText.setTypeface(menuTF)
+ gutsViewHolder.cancelText.setTypeface(menuTF)
titleText.setTypeface(titleTF)
artistText.setTypeface(artistTF)
seamlessText.setTypeface(menuTF)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 9153e17393d2..bcda485272c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -419,8 +419,10 @@ class MediaControlViewModel(
const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L
@Deprecated("Remove with media_controls_a11y_colors flag")
- const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f
- const val MEDIA_PLAYER_SCRIM_CENTER_ALPHA = 0.75f
- const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f
+ const val MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY = 0.25f
+ @Deprecated("Remove with media_controls_a11y_colors flag")
+ const val MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY = 1.0f
+ const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.65f
+ const val MEDIA_PLAYER_SCRIM_END_ALPHA = 0.75f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
index 24bb16a11fe7..3a81102699f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -27,9 +27,7 @@ object QSEvents {
private set
fun setLoggerForTesting(): UiEventLoggerFake {
- return UiEventLoggerFake().also {
- qsUiEventsLogger = it
- }
+ return UiEventLoggerFake().also { qsUiEventsLogger = it }
}
fun resetLogger() {
@@ -40,32 +38,28 @@ object QSEvents {
enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "Tile clicked. It has an instance id and a spec (or packageName)")
QS_ACTION_CLICK(387),
-
- @UiEvent(doc = "Tile secondary button clicked. " +
- "It has an instance id and a spec (or packageName)")
+ @UiEvent(
+ doc =
+ "Tile secondary button clicked. " + "It has an instance id and a spec (or packageName)"
+ )
QS_ACTION_SECONDARY_CLICK(388),
-
@UiEvent(doc = "Tile long clicked. It has an instance id and a spec (or packageName)")
QS_ACTION_LONG_PRESS(389),
-
- @UiEvent(doc = "Quick Settings panel expanded")
- QS_PANEL_EXPANDED(390),
-
- @UiEvent(doc = "Quick Settings panel collapsed")
- QS_PANEL_COLLAPSED(391),
-
- @UiEvent(doc = "Tile visible in Quick Settings panel. The tile may be in a different page. " +
- "It has an instance id and a spec (or packageName)")
+ @UiEvent(doc = "Quick Settings panel expanded") QS_PANEL_EXPANDED(390),
+ @UiEvent(doc = "Quick Settings panel collapsed") QS_PANEL_COLLAPSED(391),
+ @UiEvent(
+ doc =
+ "Tile visible in Quick Settings panel. The tile may be in a different page. " +
+ "It has an instance id and a spec (or packageName)"
+ )
QS_TILE_VISIBLE(392),
-
- @UiEvent(doc = "Quick Quick Settings panel expanded")
- QQS_PANEL_EXPANDED(393),
-
- @UiEvent(doc = "Quick Quick Settings panel collapsed")
- QQS_PANEL_COLLAPSED(394),
-
- @UiEvent(doc = "Tile visible in Quick Quick Settings panel. " +
- "It has an instance id and a spec (or packageName)")
+ @UiEvent(doc = "Quick Quick Settings panel expanded") QQS_PANEL_EXPANDED(393),
+ @UiEvent(doc = "Quick Quick Settings panel collapsed") QQS_PANEL_COLLAPSED(394),
+ @UiEvent(
+ doc =
+ "Tile visible in Quick Quick Settings panel. " +
+ "It has an instance id and a spec (or packageName)"
+ )
QQS_TILE_VISIBLE(395);
override fun getId() = _id
@@ -73,47 +67,32 @@ enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
enum class QSEditEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
- @UiEvent(doc = "Tile removed from current tiles")
- QS_EDIT_REMOVE(210),
-
- @UiEvent(doc = "Tile added to current tiles")
- QS_EDIT_ADD(211),
-
- @UiEvent(doc = "Tile moved")
- QS_EDIT_MOVE(212),
-
- @UiEvent(doc = "QS customizer open")
- QS_EDIT_OPEN(213),
-
- @UiEvent(doc = "QS customizer closed")
- QS_EDIT_CLOSED(214),
-
- @UiEvent(doc = "QS tiles reset")
- QS_EDIT_RESET(215);
+ @UiEvent(doc = "Tile removed from current tiles") QS_EDIT_REMOVE(210),
+ @UiEvent(doc = "Tile added to current tiles") QS_EDIT_ADD(211),
+ @UiEvent(doc = "Tile moved") QS_EDIT_MOVE(212),
+ @UiEvent(doc = "QS customizer open") QS_EDIT_OPEN(213),
+ @UiEvent(doc = "QS customizer closed") QS_EDIT_CLOSED(214),
+ @UiEvent(doc = "QS tiles reset") QS_EDIT_RESET(215),
+ @UiEvent(doc = "QS edit mode resize tile to large") QS_EDIT_RESIZE_LARGE(2122),
+ @UiEvent(doc = "QS edit mode resize tile to small") QS_EDIT_RESIZE_SMALL(2123);
override fun getId() = _id
}
/**
- * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger}
- * Other names for DND (Do Not Disturb) include "Zen" and "Priority mode".
+ * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger} Other names for DND (Do
+ * Not Disturb) include "Zen" and "Priority mode".
*/
enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
- @UiEvent(doc = "User selected an option on the DND dialog")
- QS_DND_CONDITION_SELECT(420),
-
+ @UiEvent(doc = "User selected an option on the DND dialog") QS_DND_CONDITION_SELECT(420),
@UiEvent(doc = "User increased countdown duration of DND from the DND dialog")
QS_DND_TIME_UP(422),
-
@UiEvent(doc = "User decreased countdown duration of DND from the DND dialog")
QS_DND_TIME_DOWN(423),
-
@UiEvent(doc = "User enabled DND from the QS DND dialog to last until manually turned off")
QS_DND_DIALOG_ENABLE_FOREVER(946),
-
@UiEvent(doc = "User enabled DND from the QS DND dialog to last until the next alarm goes off")
QS_DND_DIALOG_ENABLE_UNTIL_ALARM(947),
-
@UiEvent(doc = "User enabled DND from the QS DND dialog to last until countdown is done")
QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN(948);
@@ -121,29 +100,17 @@ enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
}
enum class QSUserSwitcherEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The current user has been switched in the detail panel")
- QS_USER_SWITCH(424),
-
- @UiEvent(doc = "User switcher QS dialog open")
- QS_USER_DETAIL_OPEN(425),
-
- @UiEvent(doc = "User switcher QS dialog closed")
- QS_USER_DETAIL_CLOSE(426),
-
- @UiEvent(doc = "User switcher QS dialog more settings pressed")
- QS_USER_MORE_SETTINGS(427),
-
- @UiEvent(doc = "The user has added a guest in the detail panel")
- QS_USER_GUEST_ADD(754),
-
+ @UiEvent(doc = "The current user has been switched in the detail panel") QS_USER_SWITCH(424),
+ @UiEvent(doc = "User switcher QS dialog open") QS_USER_DETAIL_OPEN(425),
+ @UiEvent(doc = "User switcher QS dialog closed") QS_USER_DETAIL_CLOSE(426),
+ @UiEvent(doc = "User switcher QS dialog more settings pressed") QS_USER_MORE_SETTINGS(427),
+ @UiEvent(doc = "The user has added a guest in the detail panel") QS_USER_GUEST_ADD(754),
@UiEvent(doc = "The user selected 'Start over' after switching to the existing Guest user")
QS_USER_GUEST_WIPE(755),
-
@UiEvent(doc = "The user selected 'Yes, continue' after switching to the existing Guest user")
QS_USER_GUEST_CONTINUE(756),
-
@UiEvent(doc = "The user has pressed 'Remove guest' in the detail panel")
QS_USER_GUEST_REMOVE(757);
override fun getId() = _id
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 6ad8bae05d7a..5930a24e01a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -306,6 +306,7 @@ constructor(
sceneState,
viewModel.containerViewModel.editModeViewModel.isEditing,
snapshotFlow { viewModel.expansionState }.map { it.progress },
+ snapshotFlow { viewModel.isQSExpandingOrCollapsing },
)
}
@@ -537,6 +538,10 @@ constructor(
return qqsVisible.value
}
+ override fun setQSExpandingOrCollapsing(isQSExpandingOrCollapsing: Boolean) {
+ viewModel.isQSExpandingOrCollapsing = isQSExpandingOrCollapsing
+ }
+
private fun setListenerCollections() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -877,6 +882,7 @@ private suspend fun synchronizeQsState(
state: MutableSceneTransitionLayoutState,
editMode: Flow<Boolean>,
expansion: Flow<Float>,
+ isQSExpandingOrCollapsing: Flow<Boolean>,
) {
coroutineScope {
val animationScope = this
@@ -888,31 +894,46 @@ private suspend fun synchronizeQsState(
currentTransition = null
}
- editMode.combine(expansion, ::Pair).collectLatest { (editMode, progress) ->
+ var lastValidProgress = 0f
+ combine(editMode, expansion, isQSExpandingOrCollapsing, ::Triple).collectLatest {
+ (editMode, progress, isQSExpandingOrCollapsing) ->
if (editMode && state.currentScene != SceneKeys.EditMode) {
state.setTargetScene(SceneKeys.EditMode, animationScope)?.second?.join()
} else if (!editMode && state.currentScene == SceneKeys.EditMode) {
state.setTargetScene(SceneKeys.QuickSettings, animationScope)?.second?.join()
}
+
if (!editMode) {
- when (progress) {
- 0f -> snapTo(QuickQuickSettings)
- 1f -> snapTo(QuickSettings)
- else -> {
- val transition = currentTransition
- if (transition != null) {
- transition.progress = progress
- return@collectLatest
- }
+ if (!isQSExpandingOrCollapsing) {
+ if (progress == 0f) {
+ snapTo(QuickQuickSettings)
+ return@collectLatest
+ }
- val newTransition =
- ExpansionTransition(progress).also { currentTransition = it }
- state.startTransitionImmediately(
- animationScope = animationScope,
- transition = newTransition,
- )
+ if (progress == 1f) {
+ snapTo(QuickSettings)
+ return@collectLatest
}
}
+
+ var progress = progress
+ if (progress >= 0f || progress <= 1f) {
+ lastValidProgress = progress
+ } else {
+ progress = lastValidProgress
+ }
+
+ val transition = currentTransition
+ if (transition != null) {
+ transition.progress = progress
+ return@collectLatest
+ }
+
+ val newTransition = ExpansionTransition(progress).also { currentTransition = it }
+ state.startTransitionImmediately(
+ animationScope = animationScope,
+ transition = newTransition,
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index ff84479ebcad..b829bbce2f18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -306,6 +306,8 @@ constructor(
val animateTilesExpansion: Boolean
get() = inFirstPage && !mediaSuddenlyAppearingInLandscape
+ var isQSExpandingOrCollapsing by mutableStateOf(false)
+
private val inFirstPage: Boolean
get() = inFirstPageViewModel.inFirstPage
@@ -539,6 +541,7 @@ constructor(
println("proposedTranslation", proposedTranslation)
println("expansionState", expansionState)
println("forceQS", forceQs)
+ println("isShadeExpandingOrCollapsing", isQSExpandingOrCollapsing)
printSection("Derived values") {
println("headerTranslation", headerTranslation)
println("translationScaleY", translationScaleY)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 482cd4014acf..3f279b0f7a74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -16,15 +16,18 @@
package com.android.systemui.qs.panels.domain.interactor
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.QSEditEvent
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.LargeTileSpanRepository
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.metricSpec
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -40,6 +43,7 @@ constructor(
private val repo: DefaultLargeTilesRepository,
private val currentTilesInteractor: CurrentTilesInteractor,
private val preferencesInteractor: QSPreferencesInteractor,
+ private val uiEventLogger: UiEventLogger,
largeTilesSpanRepo: LargeTileSpanRepository,
@PanelsLog private val logBuffer: LogBuffer,
@Application private val applicationScope: CoroutineScope,
@@ -70,8 +74,18 @@ constructor(
val isIcon = !largeTilesSpecs.value.contains(spec)
if (toIcon && !isIcon) {
preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
+ uiEventLogger.log(
+ /* event= */ QSEditEvent.QS_EDIT_RESIZE_SMALL,
+ /* uid= */ 0,
+ /* packageName= */ spec.metricSpec,
+ )
} else if (!toIcon && isIcon) {
preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
+ uiEventLogger.log(
+ /* event= */ QSEditEvent.QS_EDIT_RESIZE_LARGE,
+ /* uid= */ 0,
+ /* packageName= */ spec.metricSpec,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index d05837261b89..34b3324f81da 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -105,15 +105,14 @@ import com.android.systemui.util.kotlin.JavaAdapter;
import dalvik.annotation.optimization.NeverCompile;
-import dagger.Lazy;
-
-import kotlin.Unit;
-
import java.io.PrintWriter;
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
+import kotlin.Unit;
+
/** Handles QuickSettings touch handling, expansion and animation state. */
@SysUISingleton
public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable {
@@ -2366,8 +2365,16 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
return;
}
if (startTracing) {
+ if (mQs != null) {
+ mQs.setQSExpandingOrCollapsing(true);
+ }
+
monitor.begin(mPanelView, Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
} else {
+ if (mQs != null) {
+ mQs.setQSExpandingOrCollapsing(false);
+ }
+
if (wasCancelled) {
monitor.cancel(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 4adc1a5ae746..20b44d73e097 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -22,7 +22,9 @@ import android.icu.text.DateFormat
import android.icu.text.DisplayContext
import android.provider.Settings
import android.view.ViewGroup
+import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.getValue
+import androidx.compose.ui.graphics.Color
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -244,11 +246,29 @@ constructor(
/** Represents the background highlight of a header icons chip. */
sealed interface HeaderChipHighlight {
- data object None : HeaderChipHighlight
- data object Weak : HeaderChipHighlight
+ fun backgroundColor(colorScheme: ColorScheme): Color
- data object Strong : HeaderChipHighlight
+ fun foregroundColor(colorScheme: ColorScheme): Color
+
+ data object None : HeaderChipHighlight {
+ override fun backgroundColor(colorScheme: ColorScheme): Color = Color.Unspecified
+
+ override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary
+ }
+
+ data object Weak : HeaderChipHighlight {
+ override fun backgroundColor(colorScheme: ColorScheme): Color =
+ colorScheme.primary.copy(alpha = 0.1f)
+
+ override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary
+ }
+
+ data object Strong : HeaderChipHighlight {
+ override fun backgroundColor(colorScheme: ColorScheme): Color = colorScheme.secondary
+
+ override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.onSecondary
+ }
}
private fun getFormatFromPattern(pattern: String?): DateFormat {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index c1b8d9d123b9..6ebe02469f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static android.app.Flags.notificationsRedesignTemplates;
+
import android.app.Flags;
import android.app.Notification;
import android.graphics.drawable.Drawable;
@@ -427,7 +429,8 @@ public class NotificationGroupingUtil {
@Override
public void apply(View parent, View view, boolean apply, boolean reset) {
- if (reset && parent instanceof ConversationLayout) {
+ if (!notificationsRedesignTemplates()
+ && reset && parent instanceof ConversationLayout) {
ConversationLayout layout = (ConversationLayout) parent;
apply = layout.shouldHideAppName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f06565f1b6d2..32da6fff6bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -24,7 +24,7 @@ import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_
import static android.os.Flags.allowPrivateProfile;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
-import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY;
+import static android.provider.Settings.Secure.OTP_NOTIFICATION_REDACTION_LOCK_TIME;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI;
@@ -124,10 +124,10 @@ public class NotificationLockscreenUserManagerImpl implements
private static final Uri REDACT_OTP_ON_WIFI =
Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI);
- private static final Uri REDACT_OTP_IMMEDIATELY =
- Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_IMMEDIATELY);
+ private static final Uri OTP_REDACTION_LOCK_TIME =
+ Settings.Secure.getUriFor(OTP_NOTIFICATION_REDACTION_LOCK_TIME);
- private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
+ private static final long DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
TimeUnit.MINUTES.toMillis(10);
private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
@@ -316,7 +316,8 @@ public class NotificationLockscreenUserManagerImpl implements
protected final AtomicBoolean mConnectedToWifi = new AtomicBoolean(false);
protected final AtomicBoolean mRedactOtpOnWifi = new AtomicBoolean(true);
- protected final AtomicBoolean mRedactOtpImmediately = new AtomicBoolean(false);
+ protected final AtomicLong mOtpRedactionRequiredLockTimeMs =
+ new AtomicLong(DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS);
protected int mCurrentUserId = 0;
@@ -375,7 +376,7 @@ public class NotificationLockscreenUserManagerImpl implements
mLockScreenUris.add(SHOW_LOCKSCREEN);
mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
mLockScreenUris.add(REDACT_OTP_ON_WIFI);
- mLockScreenUris.add(REDACT_OTP_IMMEDIATELY);
+ mLockScreenUris.add(OTP_REDACTION_LOCK_TIME);
dumpManager.registerDumpable(this);
@@ -447,8 +448,8 @@ public class NotificationLockscreenUserManagerImpl implements
changed |= updateUserShowPrivateSettings(user.getIdentifier());
} else if (REDACT_OTP_ON_WIFI.equals(uri)) {
changed |= updateRedactOtpOnWifiSetting();
- } else if (REDACT_OTP_IMMEDIATELY.equals(uri)) {
- changed |= updateRedactOtpImmediatelySetting();
+ } else if (OTP_REDACTION_LOCK_TIME.equals(uri)) {
+ changed |= updateOtpLockTimeSetting();
}
}
@@ -487,7 +488,7 @@ public class NotificationLockscreenUserManagerImpl implements
mLockscreenSettingsObserver
);
mSecureSettings.registerContentObserverAsync(
- REDACT_OTP_IMMEDIATELY,
+ OTP_REDACTION_LOCK_TIME,
mLockscreenSettingsObserver
);
@@ -638,13 +639,13 @@ public class NotificationLockscreenUserManagerImpl implements
}
@WorkerThread
- private boolean updateRedactOtpImmediatelySetting() {
- boolean originalValue = mRedactOtpImmediately.get();
- boolean newValue = mSecureSettings.getIntForUser(
- REDACT_OTP_NOTIFICATION_IMMEDIATELY,
- 0,
- Process.myUserHandle().getIdentifier()) != 0;
- mRedactOtpImmediately.set(newValue);
+ private boolean updateOtpLockTimeSetting() {
+ long originalValue = mOtpRedactionRequiredLockTimeMs.get();
+ long newValue = mSecureSettings.getLongForUser(
+ OTP_NOTIFICATION_REDACTION_LOCK_TIME,
+ DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS,
+ Process.myUserHandle().getIdentifier());
+ mOtpRedactionRequiredLockTimeMs.set(newValue);
return originalValue != newValue;
}
@@ -832,14 +833,9 @@ public class NotificationLockscreenUserManagerImpl implements
return false;
}
- long latestTimeForRedaction;
- if (mRedactOtpImmediately.get()) {
- latestTimeForRedaction = mLastLockTime.get();
- } else {
- // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS
- // when this notification arrived, do not redact
- latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
- }
+ // If the lock screen was not already locked for at least mOtpRedactionRequiredLockTimeMs
+ // when this notification arrived, do not redact
+ long latestTimeForRedaction = mLastLockTime.get() + mOtpRedactionRequiredLockTimeMs.get();
if (ent.getSbn().getPostTime() < latestTimeForRedaction) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
index 2eae3eb4fc19..7548f6ff5bd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
@@ -22,9 +22,9 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -44,25 +44,17 @@ constructor(
@StatusBarChipsLog private val logger: LogBuffer,
) {
val ongoingCallState: StateFlow<OngoingCallModel> =
- (if (StatusBarChipsModernization.isEnabled)
- ongoingCallInteractor.ongoingCallState
- else
- repository.ongoingCallState)
+ (if (StatusBarChipsModernization.isEnabled) {
+ ongoingCallInteractor.ongoingCallState
+ } else {
+ repository.ongoingCallState
+ })
.onEach {
- logger.log(
- TAG,
- LogLevel.INFO,
- { str1 = it::class.simpleName },
- { "State: $str1" }
- )
+ logger.log(TAG, LogLevel.INFO, { str1 = it::class.simpleName }, { "State: $str1" })
}
- .stateIn(
- scope,
- SharingStarted.Lazily,
- OngoingCallModel.NoCall
- )
+ .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall)
companion object {
private val TAG = "OngoingCall".pad()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
index e3be95373698..402881d438dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
@@ -29,10 +29,10 @@ object NewStatusBarIcons {
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
- /** Is the refactor enabled */
+ /** Is the refactor enabled. Dependency on [StatusBarRootModernization] */
@JvmStatic
inline val isEnabled
- get() = Flags.newStatusBarIcons()
+ get() = Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index 0e3f103c152e..24ab6959b9e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -21,9 +21,14 @@ import static android.app.NotificationChannel.PROMOTIONS_ID;
import static android.app.NotificationChannel.RECS_ID;
import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
+import android.app.Notification;
+import android.content.Context;
+import android.os.Build;
+
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import java.util.List;
@@ -79,6 +84,43 @@ public class BundleEntry extends PipelineEntry {
public EntryAdapter getGroupRoot() {
return this;
}
+
+ @Override
+ public boolean isClearable() {
+ // TODO(b/394483200): check whether all of the children are clearable, when implemented
+ return true;
+ }
+
+ @Override
+ public int getTargetSdk() {
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+
+ @Override
+ public String getSummarization() {
+ return null;
+ }
+
+ @Override
+ public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) {
+ return Notification.COLOR_DEFAULT;
+ }
+
+ @Override
+ public boolean canPeek() {
+ return false;
+ }
+
+ @Override
+ public long getWhen() {
+ return 0;
+ }
+
+ @Override
+ public IconPack getIcons() {
+ // TODO(b/396446620): implement bundle icons
+ return null;
+ }
}
public static final List<BundleEntry> ROOT_BUNDLES = List.of(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 4df81c97e21e..6431cacf2107 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.notification.collection;
+import android.content.Context;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
@@ -59,9 +62,49 @@ public interface EntryAdapter {
EntryAdapter getGroupRoot();
/**
+ * @return whether the row can be removed with the 'Clear All' action
+ */
+ boolean isClearable();
+
+ /**
* Returns whether the entry is attached to the current shade list
*/
default boolean isAttached() {
return getParent() != null;
}
+
+ /**
+ * Returns the target sdk of the package that owns this entry.
+ */
+ int getTargetSdk();
+
+ /**
+ * Returns the summarization for this entry, if there is one
+ */
+ @Nullable String getSummarization();
+
+ /**
+ * Performs any steps needed to set or reset data before an inflation or reinflation.
+ */
+ default void prepareForInflation() {}
+
+ /**
+ * Gets a color that would have sufficient contrast on the given background color.
+ */
+ int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor);
+
+ /**
+ * Whether this entry can peek on screen as a heads up view
+ */
+ boolean canPeek();
+
+ /**
+ * Returns the visible 'time', in milliseconds, of the entry
+ */
+ long getWhen();
+
+ /**
+ * Retrieves the pack of icons associated with this entry
+ */
+ IconPack getIcons();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 90f9525c7683..698a56363465 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -78,6 +78,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.PriorityBucket;
import com.android.systemui.util.ListenerSet;
@@ -309,6 +310,47 @@ public final class NotificationEntry extends ListEntry {
}
return null;
}
+
+ @Override
+ public boolean isClearable() {
+ return NotificationEntry.this.isClearable();
+ }
+
+ @Override
+ public int getTargetSdk() {
+ return NotificationEntry.this.targetSdk;
+ }
+
+ @Override
+ public String getSummarization() {
+ return getRanking().getSummarization();
+ }
+
+ @Override
+ public void prepareForInflation() {
+ getSbn().clearPackageContext();
+ }
+
+ @Override
+ public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) {
+ return NotificationEntry.this.getContrastedColor(
+ context, isLowPriority, backgroundColor);
+ }
+
+ @Override
+ public boolean canPeek() {
+ return isStickyAndNotDemoted();
+ }
+
+ @Override
+ public long getWhen() {
+ return getSbn().getNotification().getWhen();
+ }
+
+ @Override
+ public IconPack getIcons() {
+ return NotificationEntry.this.getIcons();
+ }
}
public EntryAdapter getEntryAdapter() {
@@ -580,6 +622,7 @@ public final class NotificationEntry extends ListEntry {
}
public boolean hasFinishedInitialization() {
+ NotificationBundleUi.assertInLegacyMode();
return initializationTime != -1
&& SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
}
@@ -663,10 +706,12 @@ public final class NotificationEntry extends ListEntry {
}
public void resetInitializationTime() {
+ NotificationBundleUi.assertInLegacyMode();
initializationTime = -1;
}
public void setInitializationTime(long time) {
+ NotificationBundleUi.assertInLegacyMode();
if (initializationTime == -1) {
initializationTime = time;
}
@@ -683,9 +728,13 @@ public final class NotificationEntry extends ListEntry {
* @return {@code true} if we are a media notification
*/
public boolean isMediaNotification() {
- if (row == null) return false;
+ if (NotificationBundleUi.isEnabled()) {
+ return getSbn().getNotification().isMediaNotification();
+ } else {
+ if (row == null) return false;
- return row.isMediaRow();
+ return row.isMediaRow();
+ }
}
public boolean containsCustomViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 9c1d0735a65b..ac11c15e8bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -67,6 +67,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.util.Assert;
import com.android.systemui.util.NamedListenerSet;
@@ -1282,7 +1283,13 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
entry.getAttachState().setExcludingFilter(filter);
if (filter != null) {
// notification is removed from the list, so we reset its initialization time
- entry.resetInitializationTime();
+ if (NotificationBundleUi.isEnabled()) {
+ if (entry.getRow() != null) {
+ entry.getRow().resetInitializationTime();
+ }
+ } else {
+ entry.resetInitializationTime();
+ }
}
return filter != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 9aa5a2e32ada..7959e99f3189 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.promoted
import android.app.Flags
+import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
import android.graphics.PorterDuff
import android.view.LayoutInflater
@@ -166,7 +167,10 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private val closeButton: View? = root.findViewById(R.id.close_button)
private val conversationIconBadge: View? = root.findViewById(R.id.conversation_icon_badge)
private val conversationIcon: CachingIconView? = root.findViewById(R.id.conversation_icon)
- private val conversationText: TextView? = root.findViewById(R.id.conversation_text)
+ private val conversationText: TextView? =
+ root.findViewById(
+ if (notificationsRedesignTemplates()) R.id.title else R.id.conversation_text
+ )
private val expandButton: NotificationExpandButton? = root.findViewById(R.id.expand_button)
private val headerText: TextView? = root.findViewById(R.id.header_text)
private val headerTextDivider: View? = root.findViewById(R.id.header_text_divider)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 64cd6174a585..6f7eade1c0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -46,6 +46,7 @@ import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -117,7 +118,6 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
@@ -169,6 +169,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
+ private static final long INITIALIZATION_DELAY = 400;
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
@@ -267,7 +268,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private NotificationContentView mPublicLayout;
private NotificationContentView mPrivateLayout;
private NotificationContentView[] mLayouts;
- private int mNotificationColor;
private ExpandableNotificationRowLogger mLogger;
private String mLoggingKey;
private NotificationGuts mGuts;
@@ -352,6 +352,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
private boolean mSaveSpaceOnLockscreen;
+ // indicates when this view was first attached to a window
+ // this value will reset when the view is completely removed from the shade (ie: filtered out)
+ private long initializationTime = -1;
+
/**
* It is added for unit testing purpose.
* Please do not use it for other purposes.
@@ -625,26 +629,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
- * Marks a content view as freeable, setting it so that future inflations do not reinflate
- * and ensuring that the view is freed when it is safe to remove.
- *
- * @param inflationFlag flag corresponding to the content view to be freed
- * @deprecated By default, {@link NotificationRowContentBinder#unbindContent} will tell the
- * view hierarchy to only free when the view is safe to remove so this method is no longer
- * needed. Will remove when all uses are gone.
- */
- @Deprecated
- public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
- RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
- params.markContentViewsFreeable(inflationFlag);
- mRowContentBindStage.requestRebind(mEntry, null /* callback */);
- }
-
- /**
* Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif
* or is in an allowList).
*/
public boolean getIsNonblockable() {
+ NotificationBundleUi.assertInLegacyMode();
if (mEntry == null) {
return true;
}
@@ -666,9 +655,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
l.onNotificationUpdated(mEntry);
}
mShowingPublicInitialized = false;
- updateNotificationColor();
if (mMenuRow != null) {
- mMenuRow.onNotificationUpdated(mEntry.getSbn());
+ mMenuRow.onNotificationUpdated();
mMenuRow.setAppName(mAppName);
}
if (mIsSummaryWithChildren) {
@@ -727,15 +715,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
- * Called when the notification's ranking was changed (but nothing else changed).
- */
- public void onNotificationRankingUpdated() {
- if (mMenuRow != null) {
- mMenuRow.onNotificationUpdated(mEntry.getSbn());
- }
- }
-
- /**
* Call when bubble state has changed and the button on the notification should be updated.
*/
public void updateBubbleButton() {
@@ -746,7 +725,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@VisibleForTesting
void updateShelfIconColor() {
- StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();
+ StatusBarIconView expandedIcon = getShelfIcon();
boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
ContrastColorUtil.getInstance(mContext));
@@ -767,8 +746,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (color != Notification.COLOR_INVALID) {
return color;
} else {
- return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
- getBackgroundColorWithoutTint());
+ if (NotificationBundleUi.isEnabled()) {
+ return mEntryAdapter.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
+ getBackgroundColorWithoutTint());
+ } else {
+ return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
+ getBackgroundColorWithoutTint());
+ }
}
}
@@ -870,15 +854,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean customView = contractedView != null
&& contractedView.getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
- boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
- boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
- boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S;
+ int targetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ if (NotificationBundleUi.isEnabled()) {
+ targetSdk = mEntryAdapter.getTargetSdk();
+ } else {
+ targetSdk = mEntry.targetSdk;
+ }
+
+ boolean beforeN = targetSdk < Build.VERSION_CODES.N;
+ boolean beforeP = targetSdk < Build.VERSION_CODES.P;
+ boolean beforeS = targetSdk < Build.VERSION_CODES.S;
int smallHeight;
boolean isCallLayout = contractedView instanceof CallLayout;
boolean isMessagingLayout = contractedView instanceof MessagingLayout
|| contractedView instanceof ConversationLayout;
+ String summarization = null;
+ if (NotificationBundleUi.isEnabled()) {
+ summarization = mEntryAdapter.getSummarization();
+ } else {
+ summarization = mEntry.getRanking().getSummarization();
+ }
+
if (customView && beforeS && !mIsSummaryWithChildren) {
if (beforeN) {
smallHeight = mMaxSmallHeightBeforeN;
@@ -891,7 +889,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
smallHeight = maxExpandedHeight;
} else if (NmSummarizationUiFlag.isEnabled()
&& isMessagingLayout
- && !TextUtils.isEmpty(mEntry.getRanking().getSummarization())) {
+ && !TextUtils.isEmpty(summarization)) {
smallHeight = mMaxSmallHeightWithSummarization;
} else {
smallHeight = mMaxSmallHeight;
@@ -1524,7 +1522,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public boolean hasFinishedInitialization() {
- return getEntry().hasFinishedInitialization();
+ if (NotificationBundleUi.isEnabled()) {
+ return initializationTime != -1
+ && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
+ } else {
+ return getEntry().hasFinishedInitialization();
+ }
+ }
+
+ public void resetInitializationTime() {
+ initializationTime = -1;
+ }
+
+ public void setInitializationTime(long time) {
+ if (initializationTime == -1) {
+ initializationTime = time;
+ }
}
/**
@@ -1539,7 +1552,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return null;
}
if (mMenuRow.getMenuView() == null) {
- mMenuRow.createMenu(this, mEntry.getSbn());
+ mMenuRow.createMenu(this);
mMenuRow.setAppName(mAppName);
FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
@@ -1581,7 +1594,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (oldMenu != null) {
int menuIndex = indexOfChild(oldMenu);
removeView(oldMenu);
- mMenuRow.createMenu(ExpandableNotificationRow.this, mEntry.getSbn());
+ mMenuRow.createMenu(ExpandableNotificationRow.this);
mMenuRow.setAppName(mAppName);
addView(mMenuRow.getMenuView(), menuIndex);
}
@@ -1589,12 +1602,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
l.reinflate();
l.reInflateViews();
}
- mEntry.getSbn().clearPackageContext();
+ if (NotificationBundleUi.isEnabled()) {
+ mEntryAdapter.prepareForInflation();
+ } else {
+ mEntry.getSbn().clearPackageContext();
+ }
// TODO: Move content inflation logic out of this call
RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
params.setNeedsReinflation(true);
- var rebindEndCallback = mRebindingTracker.trackRebinding(mEntry.getKey());
+ var rebindEndCallback = mRebindingTracker.trackRebinding(NotificationBundleUi.isEnabled()
+ ? mEntryAdapter.getKey() : mEntry.getKey());
mRowContentBindStage.requestRebind(mEntry, (e) -> rebindEndCallback.onFinished());
Trace.endSection();
}
@@ -1658,20 +1676,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPrivateLayout.setSingleLineWidthIndention(indention);
}
- public int getNotificationColor() {
- return mNotificationColor;
- }
-
- public void updateNotificationColor() {
- Configuration currentConfig = getResources().getConfiguration();
- boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
-
- mNotificationColor = ContrastColorUtil.resolveContrastColor(mContext,
- mEntry.getSbn().getNotification().color,
- getBackgroundColorWithoutTint(), nightMode);
- }
-
public HybridNotificationView getSingleLineView() {
return mPrivateLayout.getSingleLineView();
}
@@ -2260,6 +2264,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mEntry = entry;
}
+ @VisibleForTesting
+ protected void setEntryAdapter(EntryAdapter entry) {
+ mEntryAdapter = entry;
+ }
+
private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
@@ -2497,7 +2506,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mTranslateableViews.get(i).setTranslationX(0);
}
invalidateOutline();
- getEntry().getIcons().getShelfIcon().setScrollX(0);
+ getShelfIcon().setScrollX(0);
}
if (mMenuRow != null) {
@@ -2616,7 +2625,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// In order to keep the shelf in sync with this swiping, we're simply translating
// it's icon by the same amount. The translation is already being used for the normal
// positioning, so we can use the scrollX instead.
- getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX);
+ getShelfIcon().setScrollX((int) -translationX);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
@@ -2843,7 +2852,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public @NonNull StatusBarIconView getShelfIcon() {
- return getEntry().getIcons().getShelfIcon();
+ if (NotificationBundleUi.isEnabled()) {
+ return getEntryAdapter().getIcons().getShelfIcon();
+ } else {
+ return mEntry.getIcons().getShelfIcon();
+ }
}
@Override
@@ -3072,8 +3085,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* except for legacy use cases.
*/
public boolean canShowHeadsUp() {
+ boolean canEntryHun = NotificationBundleUi.isEnabled()
+ ? mEntryAdapter.canPeek()
+ : mEntry.isStickyAndNotDemoted();
if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
- (!mEntry.isStickyAndNotDemoted()
+ (!canEntryHun
|| (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
return false;
}
@@ -3112,7 +3128,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
if (!mIsSummaryWithChildren && wasSummary) {
// Reset the 'when' once the row stops being a summary
- mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
+ if (NotificationBundleUi.isEnabled()) {
+ mPublicLayout.setNotificationWhen(mEntryAdapter.getWhen());
+ } else {
+ mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
+ }
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
@@ -3381,7 +3401,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
@Override
public boolean canExpandableViewBeDismissed() {
- if (areGutsExposed() || !mEntry.hasFinishedInitialization()) {
+ if (areGutsExposed() || !hasFinishedInitialization()) {
return false;
}
return canViewBeDismissed();
@@ -3405,7 +3425,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* clearability see {@link NotificationEntry#isClearable()}.
*/
public boolean canViewBeCleared() {
- return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ if (NotificationBundleUi.isEnabled()) {
+ return mEntryAdapter.isClearable()
+ && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ } else {
+ return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ }
}
private boolean shouldShowPublic() {
@@ -4082,6 +4107,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isMediaRow() {
+ NotificationBundleUi.assertInLegacyMode();
return mEntry.getSbn().getNotification().isMediaNotification();
}
@@ -4204,7 +4230,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void dump(PrintWriter pwOriginal, String[] args) {
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
// Skip super call; dump viewState ourselves
- pw.println("Notification: " + mEntry.getKey());
+ if (NotificationBundleUi.isEnabled()) {
+ pw.println("Notification: " + mEntryAdapter.getKey());
+ } else {
+ pw.println("Notification: " + mEntry.getKey());
+ }
DumpUtilsKt.withIncreasedIndent(pw, () -> {
pw.println(this);
pw.print("visibility: " + getVisibility());
@@ -4235,6 +4265,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
pw.print(", isPinned: " + isPinned());
pw.print(", expandedWhenPinned: " + mExpandedWhenPinned);
pw.print(", isMinimized: " + mIsMinimized);
+ pw.print(", isAboveShelf: " + isAboveShelf());
pw.println();
if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index b43a387a5edb..07711b6e0eb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -374,7 +374,11 @@ public class ExpandableNotificationRowController implements NotifViewController
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
- mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+ if (NotificationBundleUi.isEnabled()) {
+ mView.setInitializationTime(mClock.elapsedRealtime());
+ } else {
+ mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+ }
mPluginManager.addPluginListener(mView,
NotificationMenuRowPlugin.class, false /* Allow multiple */);
if (!SceneContainerFlag.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6e638f5de209..9a75253295d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -73,6 +73,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -436,7 +437,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
onNasFeedbackClick,
mUiEventLogger,
mDeviceProvisionedController.isDeviceProvisioned(),
- row.getIsNonblockable(),
+ NotificationBundleUi.isEnabled()
+ ? !row.getEntry().isBlockable()
+ : row.getIsNonblockable(),
mHighPriorityProvider.isHighPriority(row.getEntry()),
mAssistantFeedbackController,
mMetricsLogger,
@@ -480,7 +483,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
row.getEntry(),
onSettingsClick,
mDeviceProvisionedController.isDeviceProvisioned(),
- row.getIsNonblockable());
+ NotificationBundleUi.isEnabled()
+ ? !row.getEntry().isBlockable()
+ : row.getIsNonblockable());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index b96b224a7d2e..ab382df13d10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -186,7 +186,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
@Override
- public void createMenu(ViewGroup parent, StatusBarNotification sbn) {
+ public void createMenu(ViewGroup parent) {
mParent = (ExpandableNotificationRow) parent;
createMenuViews(true /* resetState */);
}
@@ -227,7 +227,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
@Override
- public void onNotificationUpdated(StatusBarNotification sbn) {
+ public void onNotificationUpdated() {
if (mMenuContainer == null) {
// Menu hasn't been created yet, no need to do anything.
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index 9d13ab53ec71..6a96fba8f28d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.notification.row.wrapper
import android.app.Flags
+import android.app.Flags.notificationsRedesignTemplates
import android.content.Context
import android.graphics.drawable.AnimatedImageDrawable
import android.view.View
import android.view.ViewGroup
+import androidx.core.view.isVisible
import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.MessagingGroup
@@ -53,8 +55,8 @@ class NotificationConversationTemplateViewWrapper(
private lateinit var badgeIconView: NotificationRowIconView
private lateinit var conversationBadgeBg: View
private lateinit var expandBtn: View
- private lateinit var expandBtnContainer: View
- private lateinit var imageMessageContainer: ViewGroup
+ private var expandBtnContainer: View? = null
+ private var imageMessageContainer: ViewGroup? = null
private lateinit var messageContainers: ArrayList<MessagingGroup>
private lateinit var messagingLinearLayout: MessagingLinearLayout
private lateinit var conversationTitleView: View
@@ -78,10 +80,14 @@ class NotificationConversationTemplateViewWrapper(
conversationBadgeBg =
requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
expandBtn = requireViewById(com.android.internal.R.id.expand_button)
- expandBtnContainer = requireViewById(com.android.internal.R.id.expand_button_container)
+ expandBtnContainer = findViewById(com.android.internal.R.id.expand_button_container)
importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
appName = requireViewById(com.android.internal.R.id.app_name_text)
- conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
+ conversationTitleView =
+ requireViewById(
+ if (notificationsRedesignTemplates()) com.android.internal.R.id.title
+ else com.android.internal.R.id.conversation_text
+ )
facePileTop = findViewById(com.android.internal.R.id.conversation_face_pile_top)
facePileBottom = findViewById(com.android.internal.R.id.conversation_face_pile_bottom)
facePileBottomBg =
@@ -133,11 +139,21 @@ class NotificationConversationTemplateViewWrapper(
expandable: Boolean,
onClickListener: View.OnClickListener,
requestLayout: Boolean,
- ) = conversationLayout.updateExpandability(expandable, onClickListener)
+ ) {
+ if (notificationsRedesignTemplates()) {
+ super.updateExpandability(expandable, onClickListener, requestLayout)
+ } else {
+ conversationLayout.updateExpandability(expandable, onClickListener)
+ }
+ }
override fun disallowSingleClick(x: Float, y: Float): Boolean {
val isOnExpandButton =
- expandBtnContainer.visibility == View.VISIBLE && isOnView(expandBtnContainer, x, y)
+ if (notificationsRedesignTemplates()) {
+ expandBtn.isVisible && isOnView(expandBtn, x, y)
+ } else {
+ expandBtnContainer?.visibility == View.VISIBLE && isOnView(expandBtnContainer, x, y)
+ }
return isOnExpandButton || super.disallowSingleClick(x, y)
}
@@ -158,7 +174,8 @@ class NotificationConversationTemplateViewWrapper(
// and the top level image message container.
val containers =
messageContainers.asSequence().map { it.messageContainer } +
- sequenceOf(imageMessageContainer)
+ if (notificationsRedesignTemplates()) emptySequence()
+ else sequenceOf(imageMessageContainer!!)
val drawables =
containers
.flatMap { it.children }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3d60e03d7ca4..3ff18efeae53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -6686,7 +6686,7 @@ public class NotificationStackScrollLayout
static boolean canChildBeCleared(View v) {
if (v instanceof ExpandableNotificationRow row) {
- if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
+ if (row.areGutsExposed() || !row.hasFinishedInitialization()) {
return false;
}
return row.canViewBeCleared();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 06b989aaab57..08692bea2292 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -1227,13 +1227,17 @@ public class StackScrollAlgorithm {
float baseZ = ambientState.getBaseZHeight();
if (SceneContainerFlag.isEnabled()) {
- // SceneContainer flags off this logic, and just sets the baseZ because:
+ // SceneContainer simplifies this logic, because:
// - there are no overlapping HUNs anymore, no need for multiplying their shadows
// - shadows for HUNs overlapping with the stack are now set from updateHeadsUpStates
- // - shadows for HUNs overlapping with the shelf are NOT set anymore, because it only
- // happens on AOD/Pulsing, where they're displayed on a black background so a shadow
- // wouldn't be visible.
- childViewState.setZTranslation(baseZ);
+ if (child.isPinned() || ambientState.getTrackedHeadsUpRow() == child) {
+ // set a default elevation on the HUN, which would be overridden
+ // from updateHeadsUpStates if it is displayed in the shade
+ childViewState.setZTranslation(baseZ + mPinnedZTranslationExtra);
+ } else {
+ // set baseZ for every notification
+ childViewState.setZTranslation(baseZ);
+ }
} else {
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
&& !ambientState.isDozingAndNotPulsing(child)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index c52536d2b312..cee685a3f47a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -21,7 +21,6 @@ import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
import com.android.settingslib.SignalIcon.MobileIconGroup
-import com.android.settingslib.flags.Flags
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -29,6 +28,7 @@ import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -85,6 +85,12 @@ interface MobileIconsInteractor {
/** Whether the mobile icons can be stacked vertically. */
val isStackable: StateFlow<Boolean>
+ /**
+ * Observable for the subscriptionId of the current mobile data connection. Null if we don't
+ * have a valid subscription id
+ */
+ val activeMobileDataSubscriptionId: StateFlow<Int?>
+
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
@@ -168,6 +174,9 @@ constructor(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val activeMobileDataSubscriptionId: StateFlow<Int?> =
+ mobileConnectionsRepo.activeMobileDataSubscriptionId
+
override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
mobileConnectionsRepo.activeMobileDataRepository
.flatMapLatest { it?.dataEnabled ?: flowOf(false) }
@@ -298,7 +307,7 @@ constructor(
.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
override val isStackable =
- if (Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled) {
+ if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) {
icons.flatMapLatest { icons ->
combine(icons.map { it.isNonTerrestrial }) {
it.size == 2 && it.none { isNonTerrestrial -> isNonTerrestrial }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 6176a3e9e281..288e49eac5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -63,6 +63,8 @@ constructor(
@VisibleForTesting
val reuseCache = ConcurrentHashMap<Int, Pair<MobileIconViewModel, CoroutineScope>>()
+ val activeMobileDataSubscriptionId: StateFlow<Int?> = interactor.activeMobileDataSubscriptionId
+
val subscriptionIdsFlow: StateFlow<List<Int>> =
interactor.filteredSubscriptions
.mapLatest { subscriptions ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
index a2c2a3cd1507..2c85a5150575 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
@@ -25,7 +25,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconMod
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -43,8 +43,14 @@ constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable()
initialValue = false,
)
- private val iconViewModelFlow: StateFlow<List<MobileIconViewModelCommon>> =
- mobileIconsViewModel.mobileSubViewModels
+ private val iconViewModelFlow: Flow<List<MobileIconViewModelCommon>> =
+ combine(
+ mobileIconsViewModel.mobileSubViewModels,
+ mobileIconsViewModel.activeMobileDataSubscriptionId,
+ ) { viewModels, activeSubId ->
+ // Sort to get the active subscription first, if it's set
+ viewModels.sortedByDescending { it.subscriptionId == activeSubId }
+ }
val dualSim: DualSim? by
hydrator.hydratedStateOf(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index cd320a12d577..d7348892356d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -31,6 +31,8 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
+import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipViewBinding
+import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarRootModernization
@@ -149,6 +151,7 @@ constructor(
if (!StatusBarNotifChips.isEnabled && !StatusBarChipsModernization.isEnabled) {
val primaryChipViewBinding =
OngoingActivityChipBinder.createBinding(primaryChipView)
+
launch {
viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
OngoingActivityChipBinder.bind(
@@ -156,18 +159,14 @@ constructor(
primaryChipViewBinding,
iconViewStore,
)
- if (StatusBarRootModernization.isEnabled) {
- when (primaryChipModel) {
- is OngoingActivityChipModel.Active ->
- primaryChipViewBinding.rootView.show(
- shouldAnimateChange = true
- )
- is OngoingActivityChipModel.Inactive ->
- primaryChipViewBinding.rootView.hide(
- state = View.GONE,
- shouldAnimateChange = primaryChipModel.shouldAnimate,
- )
+ if (StatusBarRootModernization.isEnabled) {
+ launch {
+ bindLegacyPrimaryOngoingActivityChipWithVisibility(
+ viewModel,
+ primaryChipModel,
+ primaryChipViewBinding,
+ )
}
} else {
when (primaryChipModel) {
@@ -213,12 +212,14 @@ constructor(
)
if (StatusBarRootModernization.isEnabled) {
- primaryChipViewBinding.rootView.adjustVisibility(
- chips.primary.toVisibilityModel()
- )
- secondaryChipViewBinding.rootView.adjustVisibility(
- chips.secondary.toVisibilityModel()
- )
+ launch {
+ bindOngoingActivityChipsWithVisibility(
+ viewModel,
+ chips,
+ primaryChipViewBinding,
+ secondaryChipViewBinding,
+ )
+ }
} else {
listener?.onOngoingActivityStatusChanged(
hasPrimaryOngoingActivity =
@@ -312,6 +313,52 @@ constructor(
}
}
+ /** Bind the (legacy) single primary ongoing activity chip with the status bar visibility */
+ private suspend fun bindLegacyPrimaryOngoingActivityChipWithVisibility(
+ viewModel: HomeStatusBarViewModel,
+ primaryChipModel: OngoingActivityChipModel,
+ primaryChipViewBinding: OngoingActivityChipViewBinding,
+ ) {
+ viewModel.canShowOngoingActivityChips.collectLatest { visible ->
+ if (!visible) {
+ primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ } else {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Active -> {
+ primaryChipViewBinding.rootView.show(shouldAnimateChange = true)
+ }
+
+ is OngoingActivityChipModel.Inactive -> {
+ primaryChipViewBinding.rootView.hide(
+ state = View.GONE,
+ shouldAnimateChange = primaryChipModel.shouldAnimate,
+ )
+ }
+ }
+ }
+ }
+ }
+
+ /** Bind the primary/secondary chips along with the home status bar's visibility */
+ private suspend fun bindOngoingActivityChipsWithVisibility(
+ viewModel: HomeStatusBarViewModel,
+ chips: MultipleOngoingActivityChipsModelLegacy,
+ primaryChipViewBinding: OngoingActivityChipViewBinding,
+ secondaryChipViewBinding: OngoingActivityChipViewBinding,
+ ) {
+ viewModel.canShowOngoingActivityChips.collectLatest { canShow ->
+ if (!canShow) {
+ primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ secondaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ } else {
+ primaryChipViewBinding.rootView.adjustVisibility(chips.primary.toVisibilityModel())
+ secondaryChipViewBinding.rootView.adjustVisibility(
+ chips.secondary.toVisibilityModel()
+ )
+ }
+ }
+ }
+
private fun SystemEventAnimationState.isAnimatingChip() =
when (this) {
AnimatingIn,
@@ -374,43 +421,42 @@ constructor(
if (visibility == View.INVISIBLE || visibility == View.GONE) {
return
}
+ alpha = 0f
visibility = state
}
// See CollapsedStatusBarFragment#hide.
private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) {
+ animate().cancel()
if (visibility == View.INVISIBLE || visibility == View.GONE) {
return
}
- val v = this
- v.animate().cancel()
if (!shouldAnimateChange) {
- v.alpha = 0f
- v.visibility = state
+ alpha = 0f
+ visibility = state
return
}
- v.animate()
+ animate()
.alpha(0f)
.setDuration(CollapsedStatusBarFragment.FADE_OUT_DURATION.toLong())
.setStartDelay(0)
.setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction { v.visibility = state }
+ .withEndAction { visibility = state }
}
// See CollapsedStatusBarFragment#show.
private fun View.show(shouldAnimateChange: Boolean) {
- if (visibility == View.VISIBLE) {
+ animate().cancel()
+ if (visibility == View.VISIBLE && alpha >= 1f) {
return
}
- val v = this
- v.animate().cancel()
- v.visibility = View.VISIBLE
+ visibility = View.VISIBLE
if (!shouldAnimateChange) {
- v.alpha = 1f
+ alpha = 1f
return
}
- v.animate()
+ animate()
.alpha(1f)
.setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION.toLong())
.setInterpolator(Interpolators.ALPHA_IN)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 9ae2cb203d91..807e90567eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -149,6 +149,9 @@ interface HomeStatusBarViewModel : Activatable {
*/
val isHomeStatusBarAllowedByScene: StateFlow<Boolean>
+ /** True if the home status bar is showing, and there is no HUN happening */
+ val canShowOngoingActivityChips: Flow<Boolean>
+
/** True if the operator name view is not hidden due to HUN or other visibility state */
val shouldShowOperatorNameView: Flow<Boolean>
val isClockVisible: Flow<VisibilityModel>
@@ -412,6 +415,15 @@ constructor(
)
.flowOn(bgDispatcher)
+ override val canShowOngoingActivityChips: Flow<Boolean> =
+ combine(
+ isHomeStatusBarAllowed,
+ keyguardInteractor.isSecureCameraActive,
+ headsUpNotificationInteractor.statusBarHeadsUpStatus,
+ ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
+ isHomeStatusBarAllowed && !isSecureCameraActive && !headsUpState.isPinned
+ }
+
override val isClockVisible: Flow<VisibilityModel> =
combine(
shouldHomeStatusBarBeVisible,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 4ff09d3bc6af..e8b50d580c33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -45,6 +45,9 @@ import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
@@ -74,10 +77,14 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -127,6 +134,9 @@ public class ShadeListBuilderTest extends SysuiTestCase {
private TestableStabilityManager mStabilityManager;
private TestableNotifFilter mFinalizeFilter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Map<String, Integer> mNextIdMap = new ArrayMap<>();
private int mNextRank = 0;
@@ -561,6 +571,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
public void testFilter_resetsInitalizationTime() {
// GIVEN a NotifFilter that filters out a specific package
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1));
@@ -584,6 +595,31 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void testFilter_resetsInitializationTime_onRow() throws Exception {
+ // GIVEN a NotifFilter that filters out a specific package
+ NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1));
+ mListBuilder.addFinalizeFilter(filter1);
+
+ // GIVEN a notification that was initialized 1 second ago that will be filtered out
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(PACKAGE_1)
+ .setId(nextId(PACKAGE_1))
+ .setRank(nextRank())
+ .build();
+ entry.setRow(new NotificationTestHelper(mContext, mDependency).createRow());
+ entry.getRow().setInitializationTime(SystemClock.elapsedRealtime() - 1000);
+ assertTrue(entry.getRow().hasFinishedInitialization());
+
+ // WHEN the pipeline is kicked off
+ mReadyForBuildListener.onBuildList(singletonList(entry), "test");
+ mPipelineChoreographer.runIfScheduled();
+
+ // THEN the entry's initialization time is reset
+ assertFalse(entry.getRow().hasFinishedInitialization());
+ }
+
+ @Test
public void testNotifFiltersCanBePreempted() {
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 1b5353127f25..24d8d1cc8fae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -75,6 +75,7 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -589,6 +590,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
public void testGetIsNonblockable() throws Exception {
ExpandableNotificationRow row =
mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 47238fedee4d..c874bc6056c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -36,6 +36,7 @@ import com.android.internal.widget.NotificationActionListLayout
import com.android.internal.widget.NotificationExpandButton
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.FeedbackIcon
+import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
@@ -82,6 +83,7 @@ class NotificationContentViewTest : SysuiTestCase() {
fakeParent =
spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
val mockEntry = createMockNotificationEntry()
+ val mockEntryAdapter = createMockNotificationEntryAdapter()
row =
spy(
when (NotificationBundleUi.isEnabled) {
@@ -92,6 +94,7 @@ class NotificationContentViewTest : SysuiTestCase() {
UserHandle.CURRENT
).apply {
entry = mockEntry
+ entryAdapter = mockEntryAdapter
}
}
false -> {
@@ -611,6 +614,7 @@ class NotificationContentViewTest : SysuiTestCase() {
whenever(this.entry).thenReturn(notificationEntry)
whenever(this.context).thenReturn(mContext)
whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
+ whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter())
}
private fun createMockNotificationEntry() =
@@ -624,6 +628,9 @@ class NotificationContentViewTest : SysuiTestCase() {
whenever(sbnMock.user).thenReturn(userMock)
}
+ private fun createMockNotificationEntryAdapter() =
+ mock<EntryAdapter>()
+
private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
val outerLayout = LinearLayout(mContext)
val innerLayout = LinearLayout(mContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 3d4c90140adb..99b99ee1860b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -427,7 +427,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
- whenever(row.getIsNonblockable()).thenReturn(false)
whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -463,7 +462,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- whenever(row.getIsNonblockable()).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -499,7 +497,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
- whenever(row.getIsNonblockable()).thenReturn(false)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -566,7 +563,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
): NotificationMenuRowPlugin.MenuItem {
val menuRow: NotificationMenuRowPlugin =
NotificationMenuRow(mContext, peopleNotificationIdentifier)
- menuRow.createMenu(row, row!!.entry.sbn)
+ menuRow.createMenu(row)
val menuItem = menuRow.getLongpressMenuItem(mContext)
Assert.assertNotNull(menuItem)
return menuItem
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6ac20d40f2dc..955de273c426 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -675,7 +675,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testClearNotifications_clearAllInProgress() {
ExpandableNotificationRow row = createClearableRow();
- when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
+ when(row.hasFinishedInitialization()).thenReturn(true);
doReturn(true).when(mStackScroller).isVisible(row);
mStackScroller.addContainerView(row);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
index 8d4db8b74061..8a6f68c5da68 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.panels.domain.interactor
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.core.FakeLogBuffer
@@ -29,6 +30,7 @@ val Kosmos.iconTilesInteractor by
defaultLargeTilesRepository,
currentTilesInteractor,
qsPreferencesInteractor,
+ uiEventLoggerFake,
largeTileSpanRepository,
FakeLogBuffer.Factory.create(),
applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
index d09d010cba2e..8ff7c7d01fb3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
@@ -76,6 +76,7 @@ object OngoingCallTestHelper {
promotedContent: PromotedNotificationContentModel? = null,
contentIntent: PendingIntent? = null,
uid: Int = DEFAULT_UID,
+ appName: String = "Fake name",
) {
if (StatusBarChipsModernization.isEnabled) {
activeNotificationListRepository.addNotif(
@@ -87,6 +88,7 @@ object OngoingCallTestHelper {
contentIntent = contentIntent,
promotedContent = promotedContent,
uid = uid,
+ appName = appName,
)
)
} else {
@@ -96,6 +98,7 @@ object OngoingCallTestHelper {
notificationIcon = statusBarChipIconView,
intent = contentIntent,
notificationKey = key,
+ appName = appName,
promotedContent = promotedContent,
)
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 9b6f205fba72..8fa82cad5c32 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -58,6 +58,8 @@ class FakeMobileIconsInteractor(
override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(DEFAULT_DATA_SUB_ID)
+ override val activeMobileDataSubscriptionId: MutableStateFlow<Int?> = MutableStateFlow(null)
+
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
new file mode 100644
index 000000000000..880ba5eee5d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.stackedMobileIconViewModel: StackedMobileIconViewModel by
+ Kosmos.Fixture { StackedMobileIconViewModel(mobileIconsViewModel) }
diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp
index edb22c0e7aa0..c53123359872 100644
--- a/packages/Vcn/framework-b/Android.bp
+++ b/packages/Vcn/framework-b/Android.bp
@@ -77,8 +77,7 @@ framework_connectivity_b_defaults_soong_config {
],
soong_config_variables: {
is_vcn_in_mainline: {
- //TODO: b/380155299 Make it Baklava when aidl tool can understand it
- min_sdk_version: "current",
+ min_sdk_version: "36",
static_libs: ["android.net.vcn.flags-aconfig-java"],
apex_available: ["com.android.tethering"],
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 4fa0d506f09e..aa82df493f84 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -101,8 +101,15 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
@Override
- public void toggleAutoclickPause() {
- // TODO(b/388872274): allows users to pause the autoclick.
+ public void toggleAutoclickPause(boolean paused) {
+ if (paused) {
+ if (mClickScheduler != null) {
+ mClickScheduler.cancel();
+ }
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.cancel();
+ }
+ }
}
};
@@ -133,7 +140,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
mAutoclickIndicatorScheduler);
}
- handleMouseMotion(event, policyFlags);
+ if (!isPaused()) {
+ handleMouseMotion(event, policyFlags);
+ }
} else if (mClickScheduler != null) {
mClickScheduler.cancel();
}
@@ -216,6 +225,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
}
+ private boolean isPaused() {
+ // TODO (b/397460424): Unpause when hovering over panel.
+ return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused();
+ }
+
/**
* Observes autoclick setting values, and updates ClickScheduler delay and indicator size
* whenever the setting value changes.
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index 23c5cc4111f6..ba3e3d14b9c6 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -79,11 +79,20 @@ public class AutoclickTypePanel {
// An interface exposed to {@link AutoclickController) to handle different actions on the panel,
// including changing autoclick type, pausing/resuming autoclick.
public interface ClickPanelControllerInterface {
- // Allows users to change a different autoclick type.
+ /**
+ * Allows users to change a different autoclick type.
+ *
+ * @param clickType The new autoclick type to use. Should be one of the values defined in
+ * {@link AutoclickType}.
+ */
void handleAutoclickTypeChange(@AutoclickType int clickType);
- // Allows users to pause/resume the autoclick.
- void toggleAutoclickPause();
+ /**
+ * Allows users to pause or resume autoclick.
+ *
+ * @param paused {@code true} to pause autoclick, {@code false} to resume.
+ */
+ void toggleAutoclickPause(boolean paused);
}
private final Context mContext;
@@ -211,6 +220,10 @@ public class AutoclickTypePanel {
mWindowManager.removeView(mContentView);
}
+ public boolean isPaused() {
+ return mPaused;
+ }
+
/** Toggles the panel expanded or collapsed state. */
private void togglePanelExpansion(@AutoclickType int clickType) {
final LinearLayout button = getButtonFromClickType(clickType);
@@ -234,6 +247,7 @@ public class AutoclickTypePanel {
private void togglePause() {
mPaused = !mPaused;
+ mClickPanelController.toggleAutoclickPause(mPaused);
ImageButton imageButton = (ImageButton) mPauseButton.getChildAt(/* index= */ 0);
if (mPaused) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 05301fdd8385..4f56483f487e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,6 +31,8 @@ import static android.os.UserHandle.getCallingUserId;
import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
+import static com.android.server.companion.association.DisassociationProcessor.REASON_API;
+import static com.android.server.companion.association.DisassociationProcessor.REASON_PKG_DATA_CLEARED;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
@@ -250,7 +252,7 @@ public class CompanionDeviceManagerService extends SystemService {
+ packageName + "]. Cleaning up CDM data...");
for (AssociationInfo association : associationsForPackage) {
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(association.getId(), REASON_PKG_DATA_CLEARED);
}
mCompanionAppBinder.onPackageChanged(userId);
@@ -426,7 +428,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void disassociate(int associationId) {
- mDisassociationProcessor.disassociate(associationId);
+ mDisassociationProcessor.disassociate(associationId, REASON_API);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index e7d1460aa66a..c5ac7c31b5c3 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,6 +18,8 @@ package com.android.server.companion;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static com.android.server.companion.association.DisassociationProcessor.REASON_SHELL;
+
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
import android.companion.Flags;
@@ -122,7 +124,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
if (association == null) {
out.println("Association doesn't exist.");
} else {
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(association.getId(), REASON_SHELL);
}
}
break;
@@ -132,7 +134,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
final List<AssociationInfo> userAssociations =
mAssociationStore.getAssociationsByUser(userId);
for (AssociationInfo association : userAssociations) {
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(association.getId(), REASON_SHELL);
}
}
break;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index f2d019bde703..ce7dcd0fa1d4 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -58,6 +58,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
@@ -164,6 +165,7 @@ public final class AssociationDiskStore {
private static final String FILE_NAME_LEGACY = "companion_device_manager_associations.xml";
private static final String FILE_NAME = "companion_device_manager.xml";
+ private static final String FILE_NAME_LAST_REMOVED_ASSOCIATION = "last_removed_association.txt";
private static final String XML_TAG_STATE = "state";
private static final String XML_TAG_ASSOCIATIONS = "associations";
@@ -268,6 +270,46 @@ public final class AssociationDiskStore {
}
}
+ /**
+ * Read the last removed association from disk.
+ */
+ public String readLastRemovedAssociation(@UserIdInt int userId) {
+ final AtomicFile file = createStorageFileForUser(
+ userId, FILE_NAME_LAST_REMOVED_ASSOCIATION);
+ StringBuilder sb = new StringBuilder();
+ int c;
+ try (FileInputStream fis = file.openRead()) {
+ while ((c = fis.read()) != -1) {
+ sb.append((char) c);
+ }
+ fis.close();
+ return sb.toString();
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "File " + file + " for user=" + userId + " doesn't exist.");
+ return null;
+ } catch (IOException e) {
+ Slog.e(TAG, "Can't read file " + file + " for user=" + userId);
+ return null;
+ }
+ }
+
+ /**
+ * Write the last removed association to disk.
+ */
+ public void writeLastRemovedAssociation(AssociationInfo association, String reason) {
+ Slog.i(TAG, "Writing last removed association=" + association.getId() + " to disk...");
+
+ final AtomicFile file = createStorageFileForUser(
+ association.getUserId(), FILE_NAME_LAST_REMOVED_ASSOCIATION);
+ writeToFileSafely(file, out -> {
+ out.write(String.valueOf(System.currentTimeMillis()).getBytes());
+ out.write(' ');
+ out.write(reason.getBytes());
+ out.write(' ');
+ out.write(association.toString().getBytes());
+ });
+ }
+
@NonNull
private static Associations readAssociationsFromFile(@UserIdInt int userId,
@NonNull AtomicFile file, @NonNull String rootTag) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index 757abd927ac8..f70c434e6b46 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -276,7 +276,7 @@ public class AssociationStore {
/**
* Remove an association.
*/
- public void removeAssociation(int id) {
+ public void removeAssociation(int id, String reason) {
Slog.i(TAG, "Removing association id=[" + id + "]...");
final AssociationInfo association;
@@ -291,6 +291,8 @@ public class AssociationStore {
writeCacheToDisk(association.getUserId());
+ mDiskStore.writeLastRemovedAssociation(association, reason);
+
Slog.i(TAG, "Done removing association.");
}
@@ -525,6 +527,14 @@ public class AssociationStore {
out.append(" ").append(a.toString()).append('\n');
}
}
+
+ out.append("Last Removed Association:\n");
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ String lastRemovedAssociation = mDiskStore.readLastRemovedAssociation(user.id);
+ if (lastRemovedAssociation != null) {
+ out.append(" ").append(lastRemovedAssociation).append('\n');
+ }
+ }
}
private void broadcastChange(@ChangeType int changeType, AssociationInfo association) {
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index 150e8da5f614..248056f32a4f 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -47,6 +47,13 @@ import com.android.server.companion.transport.CompanionTransportManager;
@SuppressLint("LongLogTag")
public class DisassociationProcessor {
+ public static final String REASON_REVOKED = "revoked";
+ public static final String REASON_SELF_IDLE = "self-idle";
+ public static final String REASON_SHELL = "shell";
+ public static final String REASON_LEGACY = "legacy";
+ public static final String REASON_API = "api";
+ public static final String REASON_PKG_DATA_CLEARED = "pkg-data-cleared";
+
private static final String TAG = "CDM_DisassociationProcessor";
private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
@@ -94,7 +101,7 @@ public class DisassociationProcessor {
* Disassociate an association by id.
*/
// TODO: also revoke notification access
- public void disassociate(int id) {
+ public void disassociate(int id, String reason) {
Slog.i(TAG, "Disassociating id=[" + id + "]...");
final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
@@ -126,7 +133,7 @@ public class DisassociationProcessor {
// Association cleanup.
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
- mAssociationStore.removeAssociation(association.getId());
+ mAssociationStore.removeAssociation(association.getId(), reason);
// If role is not in use by other associations, revoke the role.
// Do not need to remove the system role since it was pre-granted by the system.
@@ -151,7 +158,7 @@ public class DisassociationProcessor {
}
/**
- * @deprecated Use {@link #disassociate(int)} instead.
+ * @deprecated Use {@link #disassociate(int, String)} instead.
*/
@Deprecated
public void disassociate(int userId, String packageName, String macAddress) {
@@ -165,7 +172,7 @@ public class DisassociationProcessor {
mAssociationStore.getAssociationWithCallerChecks(association.getId());
- disassociate(association.getId());
+ disassociate(association.getId(), REASON_LEGACY);
}
@SuppressLint("MissingPermission")
@@ -223,7 +230,7 @@ public class DisassociationProcessor {
Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString()
+ "].");
- disassociate(id);
+ disassociate(id, REASON_SELF_IDLE);
}
}
@@ -234,7 +241,7 @@ public class DisassociationProcessor {
*
* Lastly remove the role holder for the revoked associations for the same packages.
*
- * @see #disassociate(int)
+ * @see #disassociate(int, String)
*/
private class OnPackageVisibilityChangeListener implements
ActivityManager.OnUidImportanceListener {
@@ -260,7 +267,7 @@ public class DisassociationProcessor {
int userId = UserHandle.getUserId(uid);
for (AssociationInfo association : mAssociationStore.getRevokedAssociations(userId,
packageName)) {
- disassociate(association.getId());
+ disassociate(association.getId(), REASON_REVOKED);
}
if (mAssociationStore.getRevokedAssociations().isEmpty()) {
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index d8e10f842665..7eb7072520de 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -365,7 +365,7 @@ public class ContextualSearchManagerService extends SystemService {
}
}
final ScreenshotHardwareBuffer shb = mWmInternal.takeContextualSearchScreenshot(
- (Flags.contextualSearchWindowLayer() ? csUid : -1));
+ (Flags.contextualSearchPreventSelfCapture() ? csUid : -1));
final Bitmap bm = shb != null ? shb.asBitmap() : null;
// Now that everything is fetched, putting it in the launchIntent.
if (bm != null) {
@@ -549,7 +549,7 @@ public class ContextualSearchManagerService extends SystemService {
Binder.withCleanCallingIdentity(() -> {
final ScreenshotHardwareBuffer shb =
mWmInternal.takeContextualSearchScreenshot(
- (Flags.contextualSearchWindowLayer() ? callingUid : -1));
+ (Flags.contextualSearchPreventSelfCapture() ? callingUid : -1));
final Bitmap bm = shb != null ? shb.asBitmap() : null;
if (bm != null) {
bundle.putParcelable(ContextualSearchManager.EXTRA_SCREENSHOT,
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index d2a5734f323f..b6fe0ad37078 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -49,7 +49,6 @@ import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
-import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -2443,6 +2442,7 @@ class StorageManagerService extends IStorageManager.Stub
} catch (Installer.InstallerException e) {
Slog.e(TAG, "Failed unmount mirror data", e);
}
+ extendWatchdogTimeout("#unmount might be slow");
mVold.unmount(vol.getId());
mStorageSessionController.onVolumeUnmount(vol.getImmutableVolumeInfo());
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 61c5501a7b5a..13d367a95942 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -446,6 +446,8 @@ public class OomAdjuster {
private static final int CACHING_UI_SERVICE_CLIENT_ADJ_THRESHOLD =
Flags.raiseBoundUiServiceThreshold() ? SERVICE_ADJ : PERCEPTIBLE_APP_ADJ;
+ static final long PERCEPTIBLE_TASK_TIMEOUT_MILLIS = 5 * 60 * 1000;
+
@VisibleForTesting
public static class Injector {
boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -1847,7 +1849,7 @@ public class OomAdjuster {
mHasVisibleActivities = false;
}
- void onOtherActivity() {
+ void onOtherActivity(long perceptibleTaskStoppedTimeMillis) {
if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
procState = PROCESS_STATE_CACHED_ACTIVITY;
mAdjType = "cch-act";
@@ -1856,6 +1858,28 @@ public class OomAdjuster {
"Raise procstate to cached activity: " + app);
}
}
+ if (Flags.perceptibleTasks() && adj > PERCEPTIBLE_MEDIUM_APP_ADJ) {
+ if (perceptibleTaskStoppedTimeMillis >= 0) {
+ final long now = mInjector.getUptimeMillis();
+ if (now - perceptibleTaskStoppedTimeMillis < PERCEPTIBLE_TASK_TIMEOUT_MILLIS) {
+ adj = PERCEPTIBLE_MEDIUM_APP_ADJ;
+ mAdjType = "perceptible-act";
+ if (procState > PROCESS_STATE_IMPORTANT_BACKGROUND) {
+ procState = PROCESS_STATE_IMPORTANT_BACKGROUND;
+ }
+
+ maybeSetProcessFollowUpUpdateLocked(app,
+ perceptibleTaskStoppedTimeMillis + PERCEPTIBLE_TASK_TIMEOUT_MILLIS,
+ now);
+ } else if (adj > PREVIOUS_APP_ADJ) {
+ adj = PREVIOUS_APP_ADJ;
+ mAdjType = "stale-perceptible-act";
+ if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+ procState = PROCESS_STATE_LAST_ACTIVITY;
+ }
+ }
+ }
+ }
mHasVisibleActivities = false;
}
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index b0f808b39053..25175e6bee5f 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -1120,7 +1120,8 @@ final class ProcessStateRecord {
} else if ((flags & ACTIVITY_STATE_FLAG_IS_STOPPING) != 0) {
callback.onStoppingActivity((flags & ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) != 0);
} else {
- callback.onOtherActivity();
+ final long ts = mApp.getWindowProcessController().getPerceptibleTaskStoppedTimeMillis();
+ callback.onOtherActivity(ts);
}
mCachedAdj = callback.adj;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e0fbaf43ea43..18f3500b2d56 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -4059,7 +4059,7 @@ class UserController implements Handler.Callback {
synchronized (mUserSwitchingDialogLock) {
dismissUserSwitchingDialog(null);
mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
- switchingFromSystemUserMessage, switchingToSystemUserMessage);
+ mHandler, switchingFromSystemUserMessage, switchingToSystemUserMessage);
mUserSwitchingDialog.show(onShown);
}
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index d1fcb9d1ca37..223e0b79ec0b 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -34,7 +34,6 @@ import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.Looper;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -72,7 +71,7 @@ class UserSwitchingDialog extends Dialog {
// Time to wait for the onAnimationEnd() callbacks before moving on
private static final int ANIMATION_TIMEOUT_MS = 1000;
- private final Handler mHandler = new Handler(Looper.myLooper());
+ private final Handler mHandler;
protected final UserInfo mOldUser;
protected final UserInfo mNewUser;
@@ -81,13 +80,14 @@ class UserSwitchingDialog extends Dialog {
protected final Context mContext;
private final int mTraceCookie;
- UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser,
+ UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, Handler handler,
String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
mContext = context;
mOldUser = oldUser;
mNewUser = newUser;
+ mHandler = handler;
mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
mDisableAnimations = SystemProperties.getBoolean(
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 27c384a22fb6..c8fedf3d1765 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -293,6 +293,13 @@ flag {
}
flag {
+ name: "perceptible_tasks"
+ namespace: "system_performance"
+ description: "Boost the oom_score_adj of activities in perceptible tasks"
+ bug: "370890207"
+}
+
+flag {
name: "expedite_activity_launch_on_cold_start"
namespace: "system_performance"
description: "Notify ActivityTaskManager of cold starts early to fix app launch behavior."
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 872f33484951..f4daf8761e9b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -890,10 +890,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// still need to let WindowManager know so it can update its own internal state for
// things like display cutouts.
display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
- if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED
- | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
+ if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo,
+ /* compareOnlyBasicChanges */ true)) {
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED;
}
+ logicalDisplayEventMask
+ |= updateAndGetMaskForDisplayPropertyChanges(mTempNonOverrideDisplayInfo);
}
mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED);
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index c3057ded66eb..7cc178d5ff6c 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -280,6 +280,11 @@ public class DisplayManagerFlags {
Flags::committedStateSeparateEvent
);
+ private final FlagState mDelayImplicitRrRegistrationUntilRrAccessed = new FlagState(
+ Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED,
+ Flags::delayImplicitRrRegistrationUntilRrAccessed
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -586,7 +591,6 @@ public class DisplayManagerFlags {
return mFramerateOverrideTriggersRrCallbacks.isEnabled();
}
-
/**
* @return {@code true} if the flag for sending refresh rate events only for the apps in
* foreground is enabled
@@ -604,6 +608,13 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if the flag for only explicit subscription for RR changes is enabled
+ */
+ public boolean isDelayImplicitRrRegistrationUntilRrAccessedEnabled() {
+ return mDelayImplicitRrRegistrationUntilRrAccessed.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -660,6 +671,7 @@ public class DisplayManagerFlags {
pw.println(" " + mFramerateOverrideTriggersRrCallbacks);
pw.println(" " + mRefreshRateEventForForegroundApps);
pw.println(" " + mCommittedStateSeparateEvent);
+ pw.println(" " + mDelayImplicitRrRegistrationUntilRrAccessed);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index f5451307afb7..a0064a9f5d1d 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -474,9 +474,9 @@ flag {
description: "Feature flag to trigger the RR callbacks when framerate overridding happens."
bug: "390113266"
is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -508,3 +508,14 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "delay_implicit_rr_registration_until_rr_accessed"
+ namespace: "display_manager"
+ description: "Feature flag for clients to subscribe to RR changes by either explicitly subscribing for refresh rate changes or request for refresh rate data"
+ bug: "391828526"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 508bc2f811e0..dfdd9e54fe2e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3485,7 +3485,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
|| (windowPerceptible != null && windowPerceptible == perceptible)) {
return;
}
- mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
+ mFocusedWindowPerceptible.put(windowToken, perceptible);
updateSystemUiLocked(userId);
}
});
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0fc182f3f1bb..fff812c038e7 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -42,6 +42,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE;
import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
import android.annotation.FlaggedApi;
@@ -286,7 +287,7 @@ public class PreferencesHelper implements RankingConfig {
if (!TAG_RANKING.equals(tag)) return;
final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
- boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
+ boolean upgradeForBubbles = xmlVersion >= XML_VERSION_BUBBLES_UPGRADE;
boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION);
if (mShowReviewPermissionsNotification
&& (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) {
@@ -337,15 +338,19 @@ public class PreferencesHelper implements RankingConfig {
}
boolean skipWarningLogged = false;
boolean skipGroupWarningLogged = false;
- boolean hasSAWPermission = false;
- if (upgradeForBubbles && uid != UNKNOWN_UID) {
- hasSAWPermission = mAppOps.noteOpNoThrow(
- OP_SYSTEM_ALERT_WINDOW, uid, name, null,
- "check-notif-bubble") == AppOpsManager.MODE_ALLOWED;
- }
- int bubblePref = hasSAWPermission
- ? BUBBLE_PREFERENCE_ALL
- : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
+ int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE,
+ DEFAULT_BUBBLE_PREFERENCE);
+ boolean bubbleLocked = (parser.getAttributeInt(null,
+ ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE)
+ != 0;
+ if (!bubbleLocked
+ && upgradeForBubbles
+ && uid != UNKNOWN_UID
+ && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null,
+ "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) {
+ // User hasn't changed bubble pref & the app has SAW, so allow all bubbles.
+ bubblePref = BUBBLE_PREFERENCE_ALL;
+ }
int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
// when data is loaded from disk it's loaded as USER_ALL, but restored data that
diff --git a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
index 6ce2cf738192..ab2489c81449 100644
--- a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
@@ -119,6 +119,8 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
mWakelockDescriptor = null;
+
+ initEnergyConsumerToPowerBracketMaps();
}
/**
@@ -157,9 +159,6 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
if (mPlan == null) {
mPlan = new PowerEstimationPlan(stats.getConfig());
- if (mStatsLayout.getEnergyConsumerCount() != 0) {
- initEnergyConsumerToPowerBracketMaps();
- }
}
Intermediates intermediates = new Intermediates();
@@ -255,6 +254,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor {
*/
private void initEnergyConsumerToPowerBracketMaps() {
int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
+ if (energyConsumerCount == 0) {
+ return;
+ }
+
int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount];
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index 342b864c6473..281aeb68f224 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -156,14 +156,15 @@ public final class StorageSessionController {
StorageUserConnection connection = null;
synchronized (mLock) {
connection = mConnections.get(connectionUserId);
- if (connection != null) {
- Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId);
- connection.notifyVolumeStateChanged(sessionId,
- vol.buildStorageVolume(mContext, vol.getMountUserId(), false));
- } else {
- Slog.w(TAG, "No available storage user connection for userId : "
- + connectionUserId);
- }
+ }
+
+ if (connection != null) {
+ Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId);
+ connection.notifyVolumeStateChanged(sessionId,
+ vol.buildStorageVolume(mContext, vol.getMountUserId(), false));
+ } else {
+ Slog.w(TAG, "No available storage user connection for userId : "
+ + connectionUserId);
}
}
@@ -225,16 +226,18 @@ public final class StorageSessionController {
String sessionId = vol.getId();
int userId = getConnectionUserIdForVolume(vol);
+ StorageUserConnection connection = null;
synchronized (mLock) {
- StorageUserConnection connection = mConnections.get(userId);
- if (connection != null) {
- Slog.i(TAG, "Removed session for vol with id: " + sessionId);
- connection.removeSession(sessionId);
- return connection;
- } else {
- Slog.w(TAG, "Session already removed for vol with id: " + sessionId);
- return null;
- }
+ connection = mConnections.get(userId);
+ }
+
+ if (connection != null) {
+ Slog.i(TAG, "Removed session for vol with id: " + sessionId);
+ connection.removeSession(sessionId);
+ return connection;
+ } else {
+ Slog.w(TAG, "Session already removed for vol with id: " + sessionId);
+ return null;
}
}
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 07d9ad16aca5..da6478bd3395 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -124,6 +124,8 @@ abstract class AbstractVibratorStep extends Step {
Slog.d(VibrationThread.TAG,
"Turning off vibrator " + getVibratorId());
}
+ // Make sure we ignore any pending callback from old vibration commands.
+ conductor.nextVibratorCallbackStepId(getVibratorId());
controller.off();
getVibration().stats.reportVibratorOff();
mPendingVibratorOffDeadline = 0;
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index e495af59a2f9..b3eead109999 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -73,7 +73,8 @@ final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep {
PrimitiveSegment[] primitivesArray =
primitives.toArray(new PrimitiveSegment[primitives.size()]);
- long vibratorOnResult = controller.on(primitivesArray, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(primitivesArray, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportComposePrimitives(vibratorOnResult, primitivesArray);
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
index bb8e6eed5707..7b41457b5016 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
@@ -72,7 +72,8 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
+ controller.getVibratorInfo().getId());
}
PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]);
- long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index e8952fafaf77..d003251ef307 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -71,7 +71,8 @@ final class ComposePwleVibratorStep extends AbstractComposedVibratorStep {
+ controller.getVibratorInfo().getId());
}
RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
- long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index a92ac679b0f4..b2cc1f60ca1d 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -148,7 +148,7 @@ final class ExternalVibrationSession extends Vibration
}
@Override
- public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
// ignored, external control does not expect callbacks from the vibrator
}
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 4b23216258af..88bb181781bf 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -64,7 +64,8 @@ final class PerformPrebakedVibratorStep extends AbstractComposedVibratorStep {
}
VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
- long vibratorOnResult = controller.on(prebaked, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(prebaked, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportPerformEffect(vibratorOnResult, prebaked);
diff --git a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
index 407f3d996798..d4bcc36aee18 100644
--- a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
@@ -49,7 +49,8 @@ final class PerformVendorEffectVibratorStep extends AbstractVibratorStep {
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformVendorEffectVibratorStep");
try {
- long vibratorOnResult = controller.on(effect, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(effect, getVibration().id, stepId);
vibratorOnResult = Math.min(vibratorOnResult, VENDOR_EFFECT_MAX_DURATION_MS);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportPerformVendorEffect(vibratorOnResult);
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 8478e7743183..26b9595e60cd 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
@@ -49,6 +50,10 @@ final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep {
@Override
public boolean acceptVibratorCompleteCallback(int vibratorId) {
+ if (Flags.fixVibrationThreadCallbackHandling()) {
+ // TODO: remove this method once flag removed.
+ return super.acceptVibratorCompleteCallback(vibratorId);
+ }
// Ensure the super method is called and will reset the off timeout and boolean flag.
// This is true if the vibrator was ON and this callback has the same vibratorId.
if (!super.acceptVibratorCompleteCallback(vibratorId)) {
@@ -161,7 +166,8 @@ final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep {
"Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+ duration + "ms");
}
- long vibratorOnResult = controller.on(duration, getVibration().id);
+ int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+ long vibratorOnResult = controller.on(duration, getVibration().id, stepId);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportVibratorOn(vibratorOnResult);
return vibratorOnResult;
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
index 628221b09d77..309eb8c3b099 100644
--- a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -137,13 +137,13 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec
}
@Override
- public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
if (vibrationId != mVibration.id) {
return;
}
synchronized (mLock) {
if (mConductor != null) {
- mConductor.notifyVibratorComplete(vibratorId);
+ mConductor.notifyVibratorComplete(vibratorId, stepId);
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 64b52b175252..bda3d442956b 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -218,8 +218,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub
}
@Override
- public void notifyVibratorCallback(int vibratorId, long vibrationId) {
- Slog.d(TAG, "Vibration callback received for vibration " + vibrationId
+ public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
+ Slog.d(TAG, "Vibration callback received for vibration " + vibrationId + " step " + stepId
+ " on vibrator " + vibratorId + ", ignoring...");
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index ae95a70e2a4f..23715e392580 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -106,7 +106,7 @@ interface VibrationSession {
* complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
* since its playback might have one or more interactions with the vibrator hardware.
*/
- void notifyVibratorCallback(int vibratorId, long vibrationId);
+ void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId);
/**
* Notify all synced vibrators have completed the last synchronized command during the playback
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 1e20debe156d..36e13224a476 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -30,6 +30,7 @@ import android.os.vibrator.VibrationEffectSegment;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.vibrator.VibrationSession.Status;
@@ -93,6 +94,8 @@ final class VibrationStepConductor {
private final Object mLock = new Object();
@GuardedBy("mLock")
private final IntArray mSignalVibratorsComplete;
+ @GuardedBy("mLock")
+ private final SparseIntArray mSignalVibratorStepIds;
@Nullable
@GuardedBy("mLock")
private Vibration.EndInfo mSignalCancel = null;
@@ -121,6 +124,8 @@ final class VibrationStepConductor {
this.vibratorManagerHooks = vibratorManagerHooks;
this.mSignalVibratorsComplete =
new IntArray(mDeviceAdapter.getAvailableVibratorIds().length);
+ this.mSignalVibratorStepIds =
+ new SparseIntArray(mDeviceAdapter.getAvailableVibratorIds().length);
}
@Nullable
@@ -418,7 +423,7 @@ final class VibrationStepConductor {
* <p>This is a lightweight method intended to be called directly via native callbacks.
* The state update is recorded for processing on the main execution thread (VibrationThread).
*/
- public void notifyVibratorComplete(int vibratorId) {
+ public void notifyVibratorComplete(int vibratorId, long stepId) {
// HAL callbacks may be triggered directly within HAL calls, so these notifications
// could be on the VibrationThread as it calls the HAL, or some other executor later.
// Therefore no thread assertion is made here.
@@ -428,6 +433,14 @@ final class VibrationStepConductor {
}
synchronized (mLock) {
+ if (Flags.fixVibrationThreadCallbackHandling()
+ && mSignalVibratorStepIds.get(vibratorId) != stepId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibrator " + vibratorId + " callback for step=" + stepId
+ + " ignored, current step=" + mSignalVibratorStepIds.get(vibratorId));
+ }
+ return;
+ }
mSignalVibratorsComplete.add(vibratorId);
mLock.notify();
}
@@ -645,6 +658,26 @@ final class VibrationStepConductor {
}
}
+ /**
+ * Updates and returns the next step id value to be used in vibrator commands.
+ *
+ * <p>This new step id will be kept by this conductor to filter out old callbacks that might be
+ * triggered too late by the HAL, preventing them from affecting the ongoing vibration playback.
+ */
+ public int nextVibratorCallbackStepId(int vibratorId) {
+ if (!Flags.fixVibrationThreadCallbackHandling()) {
+ return 0;
+ }
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(true);
+ }
+ synchronized (mLock) {
+ int stepId = mSignalVibratorStepIds.get(vibratorId) + 1;
+ mSignalVibratorStepIds.put(vibratorId, stepId);
+ return stepId;
+ }
+ }
+
private static CombinedVibration.Sequential toSequential(CombinedVibration effect) {
if (effect instanceof CombinedVibration.Sequential) {
return (CombinedVibration.Sequential) effect;
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index acb31ceb4027..ab13b0e88d04 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -72,7 +72,7 @@ final class VibratorController {
public interface OnVibrationCompleteListener {
/** Callback triggered when an active vibration command is complete. */
- void onComplete(int vibratorId, long vibrationId);
+ void onComplete(int vibratorId, long vibrationId, long stepId);
}
/** Representation of the vibrator state based on the interactions through this controller. */
@@ -285,11 +285,11 @@ final class VibratorController {
* @return The positive duration of the vibration started, if successful, zero if the vibrator
* do not support the input or a negative number if the operation failed.
*/
- public long on(long milliseconds, long vibrationId) {
+ public long on(long milliseconds, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on");
try {
synchronized (mLock) {
- long duration = mNativeWrapper.on(milliseconds, vibrationId);
+ long duration = mNativeWrapper.on(milliseconds, vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -310,7 +310,7 @@ final class VibratorController {
* @return The positive duration of the vibration started, if successful, zero if the vibrator
* do not support the input or a negative number if the operation failed.
*/
- public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) {
+ public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)");
synchronized (mLock) {
Parcel vendorData = Parcel.obtain();
@@ -319,7 +319,7 @@ final class VibratorController {
vendorData.setDataPosition(0);
long duration = mNativeWrapper.performVendorEffect(vendorData,
vendorEffect.getEffectStrength(), vendorEffect.getScale(),
- vendorEffect.getAdaptiveScale(), vibrationId);
+ vendorEffect.getAdaptiveScale(), vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -341,12 +341,12 @@ final class VibratorController {
* @return The positive duration of the vibration started, if successful, zero if the vibrator
* do not support the input or a negative number if the operation failed.
*/
- public long on(PrebakedSegment prebaked, long vibrationId) {
+ public long on(PrebakedSegment prebaked, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)");
try {
synchronized (mLock) {
long duration = mNativeWrapper.perform(prebaked.getEffectId(),
- prebaked.getEffectStrength(), vibrationId);
+ prebaked.getEffectStrength(), vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -367,14 +367,14 @@ final class VibratorController {
* @return The positive duration of the vibration started, if successful, zero if the vibrator
* do not support the input or a negative number if the operation failed.
*/
- public long on(PrimitiveSegment[] primitives, long vibrationId) {
+ public long on(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
return 0;
}
synchronized (mLock) {
- long duration = mNativeWrapper.compose(primitives, vibrationId);
+ long duration = mNativeWrapper.compose(primitives, vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -394,7 +394,7 @@ final class VibratorController {
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(RampSegment[] primitives, long vibrationId) {
+ public long on(RampSegment[] primitives, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
@@ -402,7 +402,8 @@ final class VibratorController {
}
synchronized (mLock) {
int braking = mVibratorInfo.getDefaultBraking();
- long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
+ long duration = mNativeWrapper.composePwle(
+ primitives, braking, vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -422,14 +423,14 @@ final class VibratorController {
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(PwlePoint[] pwlePoints, long vibrationId) {
+ public long on(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE v2)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
return 0;
}
synchronized (mLock) {
- long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId);
+ long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId, stepId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -544,26 +545,27 @@ final class VibratorController {
private static native boolean isAvailable(long nativePtr);
- private static native long on(long nativePtr, long milliseconds, long vibrationId);
+ private static native long on(long nativePtr, long milliseconds, long vibrationId,
+ long stepId);
private static native void off(long nativePtr);
private static native void setAmplitude(long nativePtr, float amplitude);
private static native long performEffect(long nativePtr, long effect, long strength,
- long vibrationId);
+ long vibrationId, long stepId);
private static native long performVendorEffect(long nativePtr, Parcel vendorData,
- long strength, float scale, float adaptiveScale, long vibrationId);
+ long strength, float scale, float adaptiveScale, long vibrationId, long stepId);
private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
- long vibrationId);
+ long vibrationId, long stepId);
private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
- int braking, long vibrationId);
+ int braking, long vibrationId, long stepId);
private static native long performPwleV2Effect(long nativePtr, PwlePoint[] effect,
- long vibrationId);
+ long vibrationId, long stepId);
private static native void setExternalControl(long nativePtr, boolean enabled);
@@ -595,8 +597,8 @@ final class VibratorController {
}
/** Turns vibrator on for given time. */
- public long on(long milliseconds, long vibrationId) {
- return on(mNativePtr, milliseconds, vibrationId);
+ public long on(long milliseconds, long vibrationId, long stepId) {
+ return on(mNativePtr, milliseconds, vibrationId, stepId);
}
/** Turns vibrator off. */
@@ -610,30 +612,31 @@ final class VibratorController {
}
/** Turns vibrator on to perform one of the supported effects. */
- public long perform(long effect, long strength, long vibrationId) {
- return performEffect(mNativePtr, effect, strength, vibrationId);
+ public long perform(long effect, long strength, long vibrationId, long stepId) {
+ return performEffect(mNativePtr, effect, strength, vibrationId, stepId);
}
/** Turns vibrator on to perform a vendor-specific effect. */
public long performVendorEffect(Parcel vendorData, long strength, float scale,
- float adaptiveScale, long vibrationId) {
+ float adaptiveScale, long vibrationId, long stepId) {
return performVendorEffect(mNativePtr, vendorData, strength, scale, adaptiveScale,
- vibrationId);
+ vibrationId, stepId);
}
/** Turns vibrator on to perform effect composed of give primitives effect. */
- public long compose(PrimitiveSegment[] primitives, long vibrationId) {
- return performComposedEffect(mNativePtr, primitives, vibrationId);
+ public long compose(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
+ return performComposedEffect(mNativePtr, primitives, vibrationId, stepId);
}
/** Turns vibrator on to perform PWLE effect composed of given primitives. */
- public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
- return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
+ public long composePwle(RampSegment[] primitives, int braking, long vibrationId,
+ long stepId) {
+ return performPwleEffect(mNativePtr, primitives, braking, vibrationId, stepId);
}
/** Turns vibrator on to perform PWLE effect composed of given points. */
- public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
- return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId);
+ public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
+ return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId, stepId);
}
/** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 75b1b202bcfd..3f5fc338ee3b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1293,14 +1293,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- private void onVibrationComplete(int vibratorId, long vibrationId) {
+ private void onVibrationComplete(int vibratorId, long vibrationId, long stepId) {
synchronized (mLock) {
if (mCurrentSession != null) {
if (DEBUG) {
- Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
- + " complete, notifying thread");
+ Slog.d(TAG, "Vibration " + vibrationId + " step " + stepId
+ + " on vibrator " + vibratorId + " complete, notifying thread");
}
- mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId);
+ mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId, stepId);
}
}
}
@@ -2100,10 +2100,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override
- public void onComplete(int vibratorId, long vibrationId) {
+ public void onComplete(int vibratorId, long vibrationId, long stepId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
- service.onVibrationComplete(vibratorId, vibrationId);
+ service.onVibrationComplete(vibratorId, vibrationId, stepId);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9692b69b1256..7b6d408fbe2c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -109,7 +109,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
-import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.hasWindowExtensionsEnabled;
@@ -321,9 +320,7 @@ import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
-import android.view.AppTransitionAnimationSpec;
import android.view.DisplayInfo;
-import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.InputApplicationHandle;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -493,6 +490,7 @@ final class ActivityRecord extends WindowToken {
private long createTime = System.currentTimeMillis();
long lastVisibleTime; // last time this activity became visible
long pauseTime; // last time we started pausing the activity
+ long mStoppedTime; // last time we completely stopped the activity
long launchTickTime; // base time for launch tick messages
long topResumedStateLossTime; // last time we reported top resumed state loss to an activity
// Last configuration reported to the activity in the client process.
@@ -2348,7 +2346,8 @@ final class ActivityRecord extends WindowToken {
// The snapshot of home is only used once because it won't be updated while screen
// is on (see {@link TaskSnapshotController#screenTurningOff}).
mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
- if ((mDisplayContent.mAppTransition.getTransitFlags()
+ final Transition transition = mTransitionController.getCollectingTransition();
+ if (transition != null && (transition.getFlags()
& WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
// Only use snapshot of home as starting window when unlocking directly.
return false;
@@ -3637,7 +3636,6 @@ final class ActivityRecord extends WindowToken {
if (DEBUG_VISIBILITY || DEBUG_TRANSITION) {
Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this);
}
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
// When finishing the activity preemptively take the snapshot before the app window
// is marked as hidden and any configuration changes take place
@@ -3739,7 +3737,6 @@ final class ActivityRecord extends WindowToken {
private void prepareActivityHideTransitionAnimation() {
final DisplayContent dc = mDisplayContent;
- dc.prepareAppTransition(TRANSIT_CLOSE);
setVisibility(false);
dc.executeAppTransition();
}
@@ -4391,13 +4388,6 @@ final class ActivityRecord extends WindowToken {
removeStartingWindow();
}
- // If app transition animation was running for this activity, then we need to ensure that
- // the app transition notifies that animations have completed in
- // DisplayContent.handleAnimatingStoppedAndTransition(), so add to that list now
- if (isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
- getDisplayContent().mNoAnimationNotifyOnTransitionFinished.add(token);
- }
-
if (delayed && !isEmpty()) {
// set the token aside because it has an active animation to be finished
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
@@ -5069,8 +5059,6 @@ final class ActivityRecord extends WindowToken {
void applyOptionsAnimation() {
if (DEBUG_TRANSITION) Slog.i(TAG, "Applying options for " + this);
if (mPendingRemoteAnimation != null) {
- mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
- mPendingRemoteAnimation);
mTransitionController.setStatusBarTransitionDelay(
mPendingRemoteAnimation.getStatusBarTransitionDelay());
} else {
@@ -5100,14 +5088,6 @@ final class ActivityRecord extends WindowToken {
IRemoteCallback finishCallback = null;
switch (animationType) {
case ANIM_CUSTOM:
- displayContent.mAppTransition.overridePendingAppTransition(
- pendingOptions.getPackageName(),
- pendingOptions.getCustomEnterResId(),
- pendingOptions.getCustomExitResId(),
- pendingOptions.getCustomBackgroundColor(),
- pendingOptions.getAnimationStartedListener(),
- pendingOptions.getAnimationFinishedListener(),
- pendingOptions.getOverrideTaskTransition());
options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
pendingOptions.getCustomBackgroundColor(),
@@ -5116,9 +5096,6 @@ final class ActivityRecord extends WindowToken {
finishCallback = pendingOptions.getAnimationFinishedListener();
break;
case ANIM_CLIP_REVEAL:
- displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getWidth(), pendingOptions.getHeight());
options = AnimationOptions.makeClipRevealAnimOptions(
pendingOptions.getStartX(), pendingOptions.getStartY(),
pendingOptions.getWidth(), pendingOptions.getHeight());
@@ -5130,9 +5107,6 @@ final class ActivityRecord extends WindowToken {
}
break;
case ANIM_SCALE_UP:
- displayContent.mAppTransition.overridePendingAppTransitionScaleUp(
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getWidth(), pendingOptions.getHeight());
options = AnimationOptions.makeScaleUpAnimOptions(
pendingOptions.getStartX(), pendingOptions.getStartY(),
pendingOptions.getWidth(), pendingOptions.getHeight(),
@@ -5148,10 +5122,6 @@ final class ActivityRecord extends WindowToken {
case ANIM_THUMBNAIL_SCALE_DOWN:
final boolean scaleUp = (animationType == ANIM_THUMBNAIL_SCALE_UP);
final HardwareBuffer buffer = pendingOptions.getThumbnail();
- displayContent.mAppTransition.overridePendingAppTransitionThumb(buffer,
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getAnimationStartedListener(),
- scaleUp);
options = AnimationOptions.makeThumbnailAnimOptions(buffer,
pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp);
startCallback = pendingOptions.getAnimationStartedListener();
@@ -5164,36 +5134,9 @@ final class ActivityRecord extends WindowToken {
break;
case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
- final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs();
- final IAppTransitionAnimationSpecsFuture specsFuture =
- pendingOptions.getSpecsFuture();
- if (specsFuture != null) {
- displayContent.mAppTransition.overridePendingAppTransitionMultiThumbFuture(
- specsFuture, pendingOptions.getAnimationStartedListener(),
- animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP);
- } else if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
- && specs != null) {
- displayContent.mAppTransition.overridePendingAppTransitionMultiThumb(
- specs, pendingOptions.getAnimationStartedListener(),
- pendingOptions.getAnimationFinishedListener(), false);
- } else {
- displayContent.mAppTransition.overridePendingAppTransitionAspectScaledThumb(
- pendingOptions.getThumbnail(),
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getWidth(), pendingOptions.getHeight(),
- pendingOptions.getAnimationStartedListener(),
- (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP));
- if (intent.getSourceBounds() == null) {
- intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
- pendingOptions.getStartY(),
- pendingOptions.getStartX() + pendingOptions.getWidth(),
- pendingOptions.getStartY() + pendingOptions.getHeight()));
- }
- }
+ // TODO(b/397847511): remove the related types from ActivityOptions.
break;
case ANIM_OPEN_CROSS_PROFILE_APPS:
- displayContent.mAppTransition
- .overridePendingAppTransitionStartCrossProfileApps();
options = AnimationOptions.makeCrossProfileAnimOptions();
break;
case ANIM_NONE:
@@ -5450,8 +5393,6 @@ final class ActivityRecord extends WindowToken {
}
private void setVisibility(boolean visible, boolean deferHidingClient) {
- final AppTransition appTransition = getDisplayContent().mAppTransition;
-
// Don't set visibility to false if we were already not visible. This prevents WM from
// adding the app to the closing app list which doesn't make sense for something that is
// already not visible. However, set visibility to true even if we are already visible.
@@ -5471,8 +5412,8 @@ final class ActivityRecord extends WindowToken {
}
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s",
- token, visible, appTransition, isVisible(), mVisibleRequested,
+ "setAppVisibility(%s, visible=%b): visible=%b mVisibleRequested=%b Callers=%s",
+ token, visible, isVisible(), mVisibleRequested,
Debug.getCallers(6));
// Before setting mVisibleRequested so we can track changes.
@@ -5569,15 +5510,6 @@ final class ActivityRecord extends WindowToken {
updateReportedVisibilityLocked();
}
- @Override
- boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
- boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
- if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- return false;
- }
- return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources);
- }
-
/**
* Update visibility to this {@link ActivityRecord}.
*
@@ -6447,6 +6379,7 @@ final class ActivityRecord extends WindowToken {
Slog.w(TAG, "Exception thrown during pause", e);
// Just in case, assume it to be stopped.
mAppStopped = true;
+ mStoppedTime = SystemClock.uptimeMillis();
ProtoLog.v(WM_DEBUG_STATES, "Stop failed; moving to STOPPED: %s", this);
setState(STOPPED, "stopIfPossible");
}
@@ -6480,6 +6413,7 @@ final class ActivityRecord extends WindowToken {
if (isStopping) {
ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPED: %s (stop complete)", this);
+ mStoppedTime = SystemClock.uptimeMillis();
setState(STOPPED, "activityStopped");
}
@@ -6639,9 +6573,7 @@ final class ActivityRecord extends WindowToken {
// starting window is drawn, the transition can start earlier. Exclude finishing and bubble
// because it may be a trampoline.
if (app == null && !finishing && !mLaunchedFromBubble
- && mVisibleRequested && !mDisplayContent.mAppTransition.isReady()
- && !mDisplayContent.mAppTransition.isRunning()
- && mDisplayContent.isNextTransitionForward()) {
+ && mVisibleRequested && mDisplayContent.isNextTransitionForward()) {
// The pending transition state will be cleared after the transition is started, so
// save the state for launching the client later (used by LaunchActivityItem).
mStartingData.mIsTransitionForward = true;
@@ -7523,7 +7455,6 @@ final class ActivityRecord extends WindowToken {
}
}
- getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);
scheduleAnimation();
// Schedule to handle the stopping and finishing activities which the animation is done
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 37783781a901..6f83822ee97a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2130,6 +2130,26 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
+ public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) {
+ enforceTaskPermission("setTaskIsPerceptible()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final Task task = mRootWindowContainer.anyTaskForId(taskId,
+ MATCH_ATTACHED_TASK_ONLY);
+ if (task == null) {
+ Slog.w(TAG, "setTaskIsPerceptible: No task to set with id=" + taskId);
+ return false;
+ }
+ task.mIsPerceptible = isPerceptible;
+ }
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public boolean removeTask(int taskId) {
mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()");
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b932ef362aca..6718ae435cd9 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -748,14 +748,9 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
&& policy.okToAnimate(true /* ignoreScreenOn */)) {
return false;
}
- // Consider unoccluding only when all unknown visibilities have been
- // resolved, as otherwise we just may be starting another occluding activity.
- final boolean isUnoccluding =
- mDisplayContent.mAppTransition.isUnoccluding()
- && mDisplayContent.mUnknownAppVisibilityController.allResolved();
- // If keyguard is showing, or we're unoccluding, force the keyguard's orientation,
+ // Use keyguard's orientation if it is showing and not occluded
// even if SystemUI hasn't updated the attrs yet.
- if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
+ if (policy.isKeyguardShowingAndNotOccluded()) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e30c24d87d20..9353cede49b2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -88,7 +88,6 @@ import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
import static android.window.DisplayAreaOrganizer.FEATURE_IME;
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTENT_RECORDING;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
@@ -369,12 +368,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private MetricsLogger mMetricsLogger;
- /**
- * List of clients without a transtiton animation that we notify once we are done
- * transitioning since they won't be notified through the app window animator.
- */
- final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>();
-
// Mapping from a token IBinder to a WindowToken object on this display.
private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
@@ -829,11 +822,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
final ActivityRecord focusedApp = mFocusedApp;
+ final boolean canReceiveKeys = w.canReceiveKeys();
ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
- w, w.mAttrs.flags, w.canReceiveKeys(),
+ w, w.mAttrs.flags, canReceiveKeys,
w.canReceiveKeysReason(false /* fromUserTouch */));
- if (!w.canReceiveKeys()) {
+ if (!canReceiveKeys) {
return false;
}
@@ -1171,8 +1165,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mFixedRotationTransitionListener = new FixedRotationTransitionListener(mDisplayId);
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
- mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
- mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
@@ -2858,13 +2850,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return isVisible() && !mRemoved && !mRemoving;
}
- @Override
- void onAppTransitionDone() {
- super.onAppTransitionDone();
- mWmService.mWindowsChanged = true;
- onTransitionFinished();
- }
-
void onTransitionFinished() {
// If the transition finished callback cannot match the token for some reason, make sure the
// rotated state is cleared if it is already invisible.
@@ -3385,9 +3370,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDeferredRemoval = false;
try {
mUnknownAppVisibilityController.clear();
- mAppTransition.removeAppTransitionTimeoutCallbacks();
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
- handleAnimatingStoppedAndTransition();
mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
@@ -3570,11 +3553,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION);
mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
- if (mTransitionController.isShellTransitionsEnabled()) {
- mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
- } else {
- mAppTransition.dumpDebug(proto, APP_TRANSITION);
- }
+ mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
if (mFocusedApp != null) {
mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
}
@@ -5634,61 +5613,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* @see AppTransition#prepareAppTransition
*/
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
- @WindowManager.TransitionFlags int flags) {
- prepareAppTransition(transit, flags);
- mTransitionController.requestTransitionIfNeeded(transit, flags, null /* trigger */, this);
+ @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
+ mTransitionController.requestTransitionIfNeeded(transit, flags, trigger, this);
}
void executeAppTransition() {
mTransitionController.setReady(this);
- if (mAppTransition.isTransitionSet()) {
- ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
- "Execute app transition: %s, displayId: %d Callers=%s",
- mAppTransition, mDisplayId, Debug.getCallers(5));
- mAppTransition.setReady();
- mWmService.mWindowPlacerLocked.requestTraversal();
- }
- }
-
- /**
- * Update pendingLayoutChanges after app transition has finished.
- */
- void handleAnimatingStoppedAndTransition() {
- int changes = 0;
-
- mAppTransition.setIdle();
-
- for (int i = mNoAnimationNotifyOnTransitionFinished.size() - 1; i >= 0; i--) {
- final IBinder token = mNoAnimationNotifyOnTransitionFinished.get(i);
- mAppTransition.notifyAppTransitionFinishedLocked(token);
- }
- mNoAnimationNotifyOnTransitionFinished.clear();
-
- mWallpaperController.hideDeferredWallpapersIfNeededLegacy();
-
- onAppTransitionDone();
-
- changes |= FINISH_LAYOUT_REDO_LAYOUT;
- ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout");
- computeImeTarget(true /* updateImeTarget */);
- mWallpaperMayChange = true;
- // Since the window list has been rebuilt, focus might have to be recomputed since the
- // actual order of windows might have changed again.
- mWmService.mFocusMayChange = true;
-
- pendingLayoutChanges |= changes;
}
/** Check if pending app transition is for activity / task launch. */
boolean isNextTransitionForward() {
// TODO(b/191375840): decouple "forwardness" from transition system.
- if (mTransitionController.isShellTransitionsEnabled()) {
- @WindowManager.TransitionType int type =
- mTransitionController.getCollectingTransitionType();
- return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
- }
- return mAppTransition.containsTransitRequest(TRANSIT_OPEN)
- || mAppTransition.containsTransitRequest(TRANSIT_TO_FRONT);
+ final @WindowManager.TransitionType int type =
+ mTransitionController.getCollectingTransitionType();
+ return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index fbe850198c50..7aa2101f516c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -631,7 +631,6 @@ public class DisplayPolicy {
mHandler.post(mAppTransitionFinished);
}
};
- displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener);
// TODO: Make it can take screenshot on external display
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6091b8334438..6d73739e5046 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -250,7 +250,7 @@ class KeyguardController {
// to the locked state before holding the sleep token again
if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
dc.requestTransitionAndLegacyPrepare(
- TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
+ TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
}
dc.mWallpaperController.adjustWallpaperWindows();
dc.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java
index 69463433827f..b3cff9c6cc3d 100644
--- a/services/core/java/com/android/server/wm/PresentationController.java
+++ b/services/core/java/com/android/server/wm/PresentationController.java
@@ -56,11 +56,6 @@ class PresentationController {
ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s",
win.getDisplayId(), win);
mPresentingDisplayIds.add(win.getDisplayId());
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
- /*notifyClients=*/ true);
- }
win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true);
}
@@ -76,11 +71,6 @@ class PresentationController {
if (displayIdIndex != -1) {
mPresentingDisplayIds.remove(displayIdIndex);
}
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
- /*notifyClients=*/ true);
- }
win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 40f16c187f20..f309372ab6a2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2103,10 +2103,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
}
- // Set a transition to ensure that we don't immediately try and update the visibility
- // of the activity entering PIP
- r.getDisplayContent().prepareAppTransition(TRANSIT_NONE);
-
transitionController.collect(task);
// Defer the windowing mode change until after the transition to prevent the activity
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6b3499a5d68c..6cd1336122be 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -54,7 +54,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -504,6 +503,17 @@ class Task extends TaskFragment {
int mOffsetYForInsets;
/**
+ * When set to true, the task will be kept at a PERCEPTIBLE_APP_ADJ, and downgraded
+ * to PREVIOUS_APP_ADJ if not in foreground for a period of time.
+ * One example use case is for desktop form factors, where it is important keep tasks in the
+ * perceptible state (rather than cached where it may be frozen) when a user moves it to the
+ * foreground.
+ * On startup, restored Tasks will not be perceptible, until user actually interacts with it
+ * (i.e. brings it to the foreground)
+ */
+ boolean mIsPerceptible = false;
+
+ /**
* Whether the compatibility overrides that change the resizability of the app should be allowed
* for the specific app.
*/
@@ -1647,8 +1657,7 @@ class Task extends TaskFragment {
// Prevent the transition from being executed too early if the top activity is
// resumed but the mVisibleRequested of any other activity is true, the transition
// should wait until next activity resumed.
- if (r.isState(RESUMED) || (r.isVisible()
- && !mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CLOSE))) {
+ if (r.isState(RESUMED) || r.isVisible()) {
r.finishIfPossible(reason, false /* oomAdj */);
} else {
r.destroyIfPossible(reason);
@@ -2324,11 +2333,6 @@ class Task extends TaskFragment {
mLastSurfaceSize.set(width, height);
}
- @VisibleForTesting
- boolean isInChangeTransition() {
- return AppTransition.isChangeTransitOld(mTransit);
- }
-
@Override
void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
@@ -3854,6 +3858,7 @@ class Task extends TaskFragment {
pw.print(ActivityInfo.resizeModeToString(mResizeMode));
pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture);
pw.print(" isResizeable="); pw.println(isResizeable());
+ pw.print(" isPerceptible="); pw.println(mIsPerceptible);
pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents);
@@ -5293,11 +5298,9 @@ class Task extends TaskFragment {
// Place a new activity at top of root task, so it is next to interact with the user.
if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
- mDisplayContent.prepareAppTransition(TRANSIT_NONE);
mTaskSupervisor.mNoAnimActivities.add(r);
mTransitionController.setNoAnimation(r);
} else {
- mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
mTaskSupervisor.mNoAnimActivities.remove(r);
}
if (newTask && !r.mLaunchTaskBehind) {
@@ -5477,7 +5480,8 @@ class Task extends TaskFragment {
Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
Task finishedTask = r.getTask();
- mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
+ mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED,
+ finishedTask);
r.finishIfPossible(reason, false /* oomAdj */);
// Also terminate any activities below it that aren't yet stopped, to avoid a situation
@@ -5695,7 +5699,6 @@ class Task extends TaskFragment {
ActivityOptions.abort(options);
}
}
- mDisplayContent.prepareAppTransition(transit);
}
final void moveTaskToFront(Task tr, boolean noAnimation, ActivityOptions options,
@@ -5747,12 +5750,9 @@ class Task extends TaskFragment {
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
if (noAnimation) {
- mDisplayContent.prepareAppTransition(TRANSIT_NONE);
mTaskSupervisor.mNoAnimActivities.add(top);
- if (mTransitionController.isShellTransitionsEnabled()) {
- mTransitionController.collect(top);
- mTransitionController.setNoAnimation(top);
- }
+ mTransitionController.collect(top);
+ mTransitionController.setNoAnimation(top);
ActivityOptions.abort(options);
} else {
updateTransitLocked(TRANSIT_TO_FRONT, options);
@@ -5862,10 +5862,6 @@ class Task extends TaskFragment {
moveTaskToBackInner(tr, transition);
});
} else {
- // Skip the transition for pinned task.
- if (!inPinnedWindowingMode()) {
- mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK);
- }
moveTaskToBackInner(tr, null /* transition */);
}
return true;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index bbda849262b2..9d18d6c4cc89 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -44,10 +44,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -56,7 +52,6 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
@@ -1682,36 +1677,15 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final DisplayContent dc = taskDisplayArea.mDisplayContent;
if (prev != null) {
if (prev.finishing) {
- if (DEBUG_TRANSITION) {
- Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev);
- }
if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- dc.prepareAppTransition(TRANSIT_CLOSE);
}
prev.setVisibility(false);
- } else {
- if (DEBUG_TRANSITION) {
- Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev);
- }
- if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
- anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- dc.prepareAppTransition(TRANSIT_OPEN,
- next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
- }
- }
- } else {
- if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
- if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+ } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- dc.prepareAppTransition(TRANSIT_OPEN);
}
+ } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+ anim = false;
}
if (anim) {
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 242883612124..e612d8ec0ce6 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -20,6 +20,7 @@ import static android.graphics.Matrix.MSCALE_X;
import static android.graphics.Matrix.MSCALE_Y;
import static android.graphics.Matrix.MSKEW_X;
import static android.graphics.Matrix.MSKEW_Y;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TPL;
@@ -35,6 +36,7 @@ import android.util.ArrayMap;
import android.util.IntArray;
import android.util.Pair;
import android.util.Size;
+import android.util.SparseArray;
import android.view.InputWindowHandle;
import android.window.ITrustedPresentationListener;
import android.window.TrustedPresentationThresholds;
@@ -251,7 +253,7 @@ public class TrustedPresentationListenerController {
Rect tmpLogicalDisplaySize = new Rect();
Matrix tmpInverseMatrix = new Matrix();
float[] tmpMatrix = new float[9];
- Region coveredRegionsAbove = new Region();
+ SparseArray<Region> coveredRegionsAboveByDisplay = new SparseArray<>();
long currTimeMs = System.currentTimeMillis();
ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length);
@@ -262,7 +264,7 @@ public class TrustedPresentationListenerController {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
continue;
}
- var displayFound = false;
+ int displayId = INVALID_DISPLAY;
tmpRectF.set(windowHandle.frame);
for (var displayHandle : mLastWindowHandles.second) {
if (displayHandle.mDisplayId == windowHandle.displayId) {
@@ -273,17 +275,18 @@ public class TrustedPresentationListenerController {
tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(),
displayHandle.mLogicalSize.getHeight());
tmpRect.intersect(tmpLogicalDisplaySize);
- displayFound = true;
+ displayId = displayHandle.mDisplayId;
break;
}
}
- if (!displayFound) {
+ if (displayId == INVALID_DISPLAY) {
ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name,
windowHandle.displayId);
continue;
}
+ Region coveredRegionsAbove = coveredRegionsAboveByDisplay.get(displayId, new Region());
var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
if (listeners != null) {
Region region = new Region();
@@ -304,6 +307,7 @@ public class TrustedPresentationListenerController {
}
coveredRegionsAbove.op(tmpRect, Region.Op.UNION);
+ coveredRegionsAboveByDisplay.put(displayId, coveredRegionsAbove);
ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s",
windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove);
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 7c88abcec7ec..9506ffeb2792 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -217,8 +217,7 @@ class WallpaperWindowToken extends WindowToken {
}
// If in a transition, defer commits for activities that are going invisible
- if (!visible && (mTransitionController.inTransition()
- || getDisplayContent().mAppTransition.isRunning())) {
+ if (!visible && mTransitionController.inTransition()) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 45202a29ba97..d0d2067ac4bc 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -16,9 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -29,24 +26,15 @@ import static android.content.pm.ActivityInfo.reverseOrientation;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowInsets.Type.InsetsType;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SYNC_ENGINE;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
-import static com.android.server.wm.AppTransition.isActivityTransitOld;
-import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld;
-import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -65,7 +53,6 @@ import static com.android.server.wm.WindowContainerProto.VISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
import android.annotation.ColorInt;
@@ -81,10 +68,8 @@ import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.Pair;
import android.util.Pools;
import android.util.RotationUtils;
import android.util.Slog;
@@ -102,14 +87,11 @@ import android.view.SurfaceControl.Builder;
import android.view.SurfaceControlViewHost;
import android.view.WindowManager;
import android.view.WindowManager.TransitionOldType;
-import android.view.animation.Animation;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.common.LogLevel;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -1315,31 +1297,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
- * Returns true if the container or one of its children as some content it can display or wants
- * to display (e.g. app views or saved surface).
- *
- * NOTE: While this method will return true if the there is some content to display, it doesn't
- * mean the container is visible. Use {@link #isVisible()} to determine if the container is
- * visible.
- */
- boolean hasContentToDisplay() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mChildren.get(i);
- if (wc.hasContentToDisplay()) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Returns true if the container or one of its children is considered visible from the
* WindowManager perspective which usually means valid surface and some other internal state
* are true.
*
* NOTE: While this method will return true if the surface is visible, it doesn't mean the
- * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if
- * the container has any content to display.
+ * client has actually displayed any content.
*/
boolean isVisible() {
// TODO: Will this be more correct if it checks the visibility of its parents?
@@ -1480,13 +1443,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
- void onAppTransitionDone() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mChildren.get(i);
- wc.onAppTransitionDone();
- }
- }
-
/**
* Called when this container or one of its descendants changed its requested orientation, and
* wants this container to handle it or pass it to its parent.
@@ -3039,264 +2995,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
getRelativePosition(outPosition);
}
- /**
- * Applies the app transition animation according the given the layout properties in the
- * window hierarchy.
- *
- * @param lp The layout parameters of the window.
- * @param transit The app transition type indicates what kind of transition to be applied.
- * @param enter Whether the app transition is entering transition or not.
- * @param isVoiceInteraction Whether the container is participating in voice interaction or not.
- * @param sources {@link ActivityRecord}s which causes this app transition animation.
- *
- * @return {@code true} when the container applied the app transition, {@code false} if the
- * app transition is disabled or skipped.
- *
- * @see #getAnimationAdapter
- */
- boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,
- boolean enter, boolean isVoiceInteraction,
- @Nullable ArrayList<WindowContainer> sources) {
- if (mWmService.mDisableTransitionAnimation) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: transition animation is disabled or skipped. "
- + "container=%s", this);
- cancelAnimation();
- return false;
- }
-
- // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
- // to animate and it can cause strange artifacts when we unfreeze the display if some
- // different animation is running.
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
- if (okToAnimate()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: transit=%s, enter=%b, wc=%s",
- AppTransition.appTransitionOldToString(transit), enter, this);
- applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
- } else {
- cancelAnimation();
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- return isAnimating();
- }
-
- /**
- * Gets the {@link AnimationAdapter} according the given window layout properties in the window
- * hierarchy.
- *
- * @return The return value will always contain two elements, one for normal animations and the
- * other for thumbnail animation, both can be {@code null}.
- *
- * @See com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord
- * @See LocalAnimationAdapter
- */
- Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
- @TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) {
- final Pair<AnimationAdapter, AnimationAdapter> resultAdapters;
- final int appRootTaskClipMode = getDisplayContent().mAppTransition.getAppRootTaskClipMode();
-
- // Separate position and size for use in animators.
- final Rect screenBounds = getAnimationBounds(appRootTaskClipMode);
- mTmpRect.set(screenBounds);
- getAnimationPosition(mTmpPoint);
- mTmpRect.offsetTo(0, 0);
-
- final boolean isChanging = AppTransition.isChangeTransitOld(transit);
-
- if (isChanging) {
- final float durationScale = mWmService.getTransitionAnimationScaleLocked();
- final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
- mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
-
- final AnimationAdapter adapter = new LocalAnimationAdapter(
- new WindowChangeAnimationSpec(null /* startBounds */, mTmpRect,
- displayInfo, durationScale, true /* isAppAnimation */,
- false /* isThumbnail */),
- getSurfaceAnimationRunner());
-
- final AnimationAdapter thumbnailAdapter = null;
- resultAdapters = new Pair<>(adapter, thumbnailAdapter);
- mTransit = transit;
- mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
- } else {
- mNeedsAnimationBoundsLayer = (appRootTaskClipMode == ROOT_TASK_CLIP_AFTER_ANIM);
- final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
-
- if (a != null) {
- // Only apply corner radius to animation if we're not in multi window mode.
- // We don't want rounded corners when in pip or split screen.
- final float windowCornerRadius = !inMultiWindowMode()
- ? getDisplayContent().getWindowCornerRadius()
- : 0;
- if (asActivityRecord() != null
- && asActivityRecord().isNeedsLetterboxedAnimation()) {
- asActivityRecord().getLetterboxInnerBounds(mTmpRect);
- }
- AnimationAdapter adapter = new LocalAnimationAdapter(
- new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
- getDisplayContent().mAppTransition.canSkipFirstFrame(),
- appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
- getSurfaceAnimationRunner());
-
- resultAdapters = new Pair<>(adapter, null);
- mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP
- || AppTransition.isClosingTransitOld(transit);
- mTransit = transit;
- mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
- } else {
- resultAdapters = new Pair<>(null, null);
- }
- }
- return resultAdapters;
- }
-
- protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
- @TransitionOldType int transit, boolean isVoiceInteraction,
- @Nullable ArrayList<WindowContainer> sources) {
- final Task task = asTask();
- if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) {
- final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
- final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null
- && imeTarget.getWindow().getTask() == task;
- // Attach and show the IME screenshot when the task is the IME target and performing
- // task closing transition to the next task.
- if (isImeLayeringTarget && AppTransition.isTaskCloseTransitOld(transit)) {
- mDisplayContent.showImeScreenshot();
- }
- }
- final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
- transit, enter, isVoiceInteraction);
- AnimationAdapter adapter = adapters.first;
- AnimationAdapter thumbnailAdapter = adapters.second;
- if (adapter != null) {
- if (sources != null) {
- mSurfaceAnimationSources.addAll(sources);
- }
-
- AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
-
- // Check if the animation requests to show background color for Activity and embedded
- // TaskFragment.
- final ActivityRecord activityRecord = asActivityRecord();
- final TaskFragment taskFragment = asTaskFragment();
- if (adapter.getShowBackground()
- // Check if it is Activity transition.
- && ((activityRecord != null && isActivityTransitOld(transit))
- // Check if it is embedded TaskFragment transition.
- || (taskFragment != null && taskFragment.isEmbedded()
- && isTaskFragmentTransitOld(transit)))) {
- final @ColorInt int backgroundColorForTransition;
- if (adapter.getBackgroundColor() != 0) {
- // If available use the background color provided through getBackgroundColor
- // which if set originates from a call to overridePendingAppTransition.
- backgroundColorForTransition = adapter.getBackgroundColor();
- } else {
- final TaskFragment organizedTf = activityRecord != null
- ? activityRecord.getOrganizedTaskFragment()
- : taskFragment.getOrganizedTaskFragment();
- if (organizedTf != null && organizedTf.getAnimationParams()
- .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
- // This window is embedded and has an animation background color set on the
- // TaskFragment. Pass this color with this window, so the handler can use it
- // as the animation background color if needed,
- backgroundColorForTransition = organizedTf.getAnimationParams()
- .getAnimationBackgroundColor();
- } else {
- // Otherwise default to the window's background color if provided through
- // the theme as the background color for the animation - the top most window
- // with a valid background color and showBackground set takes precedence.
- final Task parentTask = activityRecord != null
- ? activityRecord.getTask()
- : taskFragment.getTask();
- backgroundColorForTransition = parentTask.getTaskDescription()
- .getBackgroundColor();
- }
- }
- // Set to opaque for animation background to prevent it from exposing the blank
- // background or content below.
- animationRunnerBuilder.setTaskBackgroundColor(ColorUtils.setAlphaComponent(
- backgroundColorForTransition, 255));
- }
-
- animationRunnerBuilder.build()
- .startAnimation(getPendingTransaction(), adapter, !isVisible(),
- ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
-
- if (adapter.getShowWallpaper()) {
- getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- }
- }
- }
-
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
return mWmService.mSurfaceAnimationRunner;
}
- private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
- boolean isVoiceInteraction) {
- if ((isOrganized()
- // TODO(b/161711458): Clean-up when moved to shell.
- && getWindowingMode() != WINDOWING_MODE_FULLSCREEN
- && getWindowingMode() != WINDOWING_MODE_FREEFORM
- && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) {
- return null;
- }
-
- final DisplayContent displayContent = getDisplayContent();
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final int width = displayInfo.appWidth;
- final int height = displayInfo.appHeight;
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: container=%s", this);
-
- // Determine the visible rect to calculate the thumbnail clip with
- // getAnimationFrames.
- final Rect frame = new Rect(0, 0, width, height);
- final Rect displayFrame = new Rect(0, 0,
- displayInfo.logicalWidth, displayInfo.logicalHeight);
- final Rect insets = new Rect();
- final Rect stableInsets = new Rect();
- final Rect surfaceInsets = new Rect();
- getAnimationFrames(frame, insets, stableInsets, surfaceInsets);
-
- if (mLaunchTaskBehind) {
- // Differentiate the two animations. This one which is briefly on the screen
- // gets the !enter animation, and the other one which remains on the
- // screen gets the enter animation. Both appear in the mOpeningApps set.
- enter = false;
- }
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
- "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s "
- + "surfaceInsets=%s",
- AppTransition.appTransitionOldToString(transit), enter, frame, insets,
- surfaceInsets);
- final Configuration displayConfig = displayContent.getConfiguration();
- final Animation a = getDisplayContent().mAppTransition.loadAnimation(lp, transit, enter,
- displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
- surfaceInsets, stableInsets, isVoiceInteraction, inFreeformWindowingMode(), this);
- if (a != null) {
- if (a != null) {
- // Setup the maximum app transition duration to prevent malicious app may set a long
- // animation duration or infinite repeat counts for the app transition through
- // ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition.
- a.restrictDuration(MAX_APP_TRANSITION_DURATION);
- }
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
- ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s",
- a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20));
- }
- final int containingWidth = frame.width();
- final int containingHeight = frame.height();
- a.initialize(containingWidth, containingHeight, width, height);
- a.scaleCurrentDuration(mWmService.getTransitionAnimationScaleLocked());
- }
- return a;
- }
-
boolean canCreateRemoteAnimationTarget() {
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0b20911bcab2..8aed91b2dc66 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -157,6 +157,7 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -1820,8 +1821,28 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean hideSystemAlertWindows = shouldHideNonSystemOverlayWindow(win);
win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
- res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
- outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
+ // Only a presentation window needs a transition because its visibility affets the
+ // lifecycle of apps below (b/390481865).
+ if (enablePresentationForConnectedDisplays() && win.isPresentation()) {
+ Transition transition = null;
+ if (!win.mTransitionController.isCollecting()) {
+ transition = win.mTransitionController.createAndStartCollecting(TRANSIT_OPEN);
+ }
+ win.mTransitionController.collect(win.mToken);
+ res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
+ outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
+ // A presentation hides all activities behind on the same display.
+ win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ win.mTransitionController.getCollectingTransition().setReady(win.mToken, true);
+ if (transition != null) {
+ win.mTransitionController.requestStartTransition(transition, null,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ } else {
+ res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
+ outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
+ }
}
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 30dde543b9d4..270de0197a4e 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -37,6 +37,7 @@ import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STARTED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -344,6 +345,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
*/
private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
+ /**
+ * The most recent timestamp of when one of this process's stopped activities in a
+ * perceptible task became stopped. Written by window manager and read by activity manager.
+ */
+ private volatile long mPerceptibleTaskStoppedTimeMillis = Long.MIN_VALUE;
+
public WindowProcessController(@NonNull ActivityTaskManagerService atm,
@NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
@NonNull WindowProcessListener listener) {
@@ -475,8 +482,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
r.detachFromProcess();
if (r.isVisibleRequested()) {
hasVisibleActivity = true;
+ Task finishingTask = r.getTask();
r.mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE,
- TRANSIT_FLAG_APP_CRASHED);
+ TRANSIT_FLAG_APP_CRASHED, finishingTask);
}
r.destroyIfPossible("handleAppCrashed");
}
@@ -1228,6 +1236,17 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return mActivityStateFlags;
}
+ /**
+ * Returns the most recent timestamp when one of this process's stopped activities in a
+ * perceptible task became stopped. It should only be called if {@link #hasActivities}
+ * returns {@code true} and {@link #getActivityStateFlags} does not have any of
+ * the ACTIVITY_STATE_FLAG_IS_(VISIBLE|PAUSING_OR_PAUSED|STOPPING) bit set.
+ */
+ @HotPath(caller = HotPath.OOM_ADJUSTMENT)
+ public long getPerceptibleTaskStoppedTimeMillis() {
+ return mPerceptibleTaskStoppedTimeMillis;
+ }
+
void computeProcessActivityState() {
// Since there could be more than one activities in a process record, we don't need to
// compute the OomAdj with each of them, just need to find out the activity with the
@@ -1239,6 +1258,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
int minTaskLayer = Integer.MAX_VALUE;
int stateFlags = 0;
int nonOccludedRatio = 0;
+ long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE;
final boolean wasResumed = hasResumedActivity();
final boolean wasAnyVisible = (mActivityStateFlags
& (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
@@ -1287,6 +1307,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
bestInvisibleState = STOPPING;
// Not "finishing" if any of activity isn't finishing.
allStoppingFinishing &= r.finishing;
+ } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) {
+ if (task.mIsPerceptible) {
+ perceptibleTaskStoppedTimeMillis =
+ Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis);
+ }
}
}
}
@@ -1324,6 +1349,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
}
mActivityStateFlags = stateFlags;
+ mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis;
final boolean anyVisible = (stateFlags
& (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9f1289b2c12a..bfedc90497ae 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -95,6 +95,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
@@ -182,6 +183,7 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
import android.annotation.CallSuper;
@@ -1696,17 +1698,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|| mActivityRecord.isStartingWindowDisplayed());
}
- @Override
- boolean hasContentToDisplay() {
- if (!isDrawn() && (mViewVisibility == View.VISIBLE
- || (isAnimating(TRANSITION | PARENTS)
- && !getDisplayContent().mAppTransition.isTransitionSet()))) {
- return true;
- }
-
- return super.hasContentToDisplay();
- }
-
private boolean isVisibleByPolicyOrInsets() {
return isVisibleByPolicy()
// If we don't have a provider, this window isn't used as a window generating
@@ -2297,11 +2288,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
dc.updateImeInputAndControlTarget(null);
}
- final int type = mAttrs.type;
-
- if (isPresentation()) {
- mWmService.mPresentationController.onPresentationRemoved(this);
- }
// Check if window provides non decor insets before clearing its provided insets.
final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets();
@@ -2442,11 +2428,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- removeImmediately();
- mWmService.updateFocusedWindowLocked(isFocused()
- ? UPDATE_FOCUS_REMOVING_FOCUS
- : UPDATE_FOCUS_NORMAL,
- true /*updateInputWindows*/);
+ // Only a presentation window needs a transition because its visibility affets the
+ // lifecycle of apps below (b/390481865).
+ if (enablePresentationForConnectedDisplays() && isPresentation()) {
+ Transition transition = null;
+ if (!mTransitionController.isCollecting()) {
+ transition = mTransitionController.createAndStartCollecting(TRANSIT_CLOSE);
+ }
+ mTransitionController.collect(mToken);
+ mAnimatingExit = true;
+ mRemoveOnExit = true;
+ mToken.setVisibleRequested(false);
+ mWmService.mPresentationController.onPresentationRemoved(this);
+ // A presentation hides all activities behind on the same display.
+ mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ mTransitionController.getCollectingTransition().setReady(mToken, true);
+ if (transition != null) {
+ mTransitionController.requestStartTransition(transition, null,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ } else {
+ removeImmediately();
+ mWmService.updateFocusedWindowLocked(isFocused()
+ ? UPDATE_FOCUS_REMOVING_FOCUS
+ : UPDATE_FOCUS_NORMAL,
+ true /*updateInputWindows*/);
+ }
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index d26539c377a9..95776088aad8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -204,6 +204,7 @@ cc_defaults {
"android.frameworks.sensorservice-V1-ndk",
"android.frameworks.stats@1.0",
"android.frameworks.stats-V2-ndk",
+ "android.os.vibrator.flags-aconfig-cc",
"android.system.suspend.control-V1-cpp",
"android.system.suspend.control.internal-cpp",
"android.system.suspend-V1-ndk",
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index abd4cd25cf68..534dbb1f6cf1 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -21,6 +21,7 @@
#include <android/binder_parcel_jni.h>
#include <android/hardware/vibrator/1.3/IVibrator.h>
#include <android/persistable_bundle_aidl.h>
+#include <android_os_vibrator.h>
#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -143,21 +144,23 @@ public:
return mHal->doWithRetry(fn, functionName);
}
- std::function<void()> createCallback(jlong vibrationId) {
+ std::function<void()> createCallback(jlong vibrationId, jlong stepId) {
auto callbackId = ++mCallbackId;
- return [vibrationId, callbackId, this]() {
+ return [vibrationId, stepId, callbackId, this]() {
auto currentCallbackId = mCallbackId.load();
- if (currentCallbackId != callbackId) {
- // This callback is from an older HAL call that is no longer relevant to the service
+ if (!android_os_vibrator_fix_vibration_thread_callback_handling() &&
+ currentCallbackId != callbackId) {
+ // This callback is from an older HAL call that is no longer relevant
return;
}
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
- jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId,
- vibrationId);
+ jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId, vibrationId,
+ stepId);
};
}
void disableOldCallbacks() {
+ // TODO remove this once android_os_vibrator_fix_vibration_thread_callback_handling removed
mCallbackId++;
}
@@ -165,6 +168,7 @@ private:
const std::shared_ptr<vibrator::HalController> mHal;
const int32_t mVibratorId;
const jobject mCallbackListener;
+ // TODO remove this once android_os_vibrator_fix_vibration_thread_callback_handling removed
std::atomic<int64_t> mCallbackId;
};
@@ -273,13 +277,13 @@ static jboolean vibratorIsAvailable(JNIEnv* env, jclass /* clazz */, jlong ptr)
}
static jlong vibratorOn(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong timeoutMs,
- jlong vibrationId) {
+ jlong vibrationId, jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorOn failed because native wrapper was not initialized");
return -1;
}
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto onFn = [timeoutMs, &callback](vibrator::HalWrapper* hal) {
return hal->on(std::chrono::milliseconds(timeoutMs), callback);
};
@@ -324,7 +328,7 @@ static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong pt
}
static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong effect,
- jlong strength, jlong vibrationId) {
+ jlong strength, jlong vibrationId, jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformEffect failed because native wrapper was not initialized");
@@ -332,7 +336,7 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j
}
Aidl::Effect effectType = static_cast<Aidl::Effect>(effect);
Aidl::EffectStrength effectStrength = static_cast<Aidl::EffectStrength>(strength);
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto performEffectFn = [effectType, effectStrength, &callback](vibrator::HalWrapper* hal) {
return hal->performEffect(effectType, effectStrength, callback);
};
@@ -342,7 +346,7 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j
static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
jobject vendorData, jlong strength, jfloat scale,
- jfloat adaptiveScale, jlong vibrationId) {
+ jfloat adaptiveScale, jlong vibrationId, jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized");
@@ -350,7 +354,7 @@ static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong
}
Aidl::VendorEffect effect =
vendorEffectFromJavaParcel(env, vendorData, strength, scale, adaptiveScale);
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) {
return hal->performVendorEffect(effect, callback);
};
@@ -359,7 +363,8 @@ static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong
}
static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobjectArray composition, jlong vibrationId) {
+ jobjectArray composition, jlong vibrationId,
+ jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformComposedEffect failed because native wrapper was not initialized");
@@ -371,7 +376,7 @@ static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlon
jobject element = env->GetObjectArrayElement(composition, i);
effects.push_back(effectFromJavaPrimitive(env, element));
}
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto performComposedEffectFn = [&effects, &callback](vibrator::HalWrapper* hal) {
return hal->performComposedEffect(effects, callback);
};
@@ -381,7 +386,8 @@ static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlon
}
static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobjectArray waveform, jint brakingId, jlong vibrationId) {
+ jobjectArray waveform, jint brakingId, jlong vibrationId,
+ jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformPwleEffect failed because native wrapper was not initialized");
@@ -406,7 +412,7 @@ static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong pt
}
}
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto performPwleEffectFn = [&primitives, &callback](vibrator::HalWrapper* hal) {
return hal->performPwleEffect(primitives, callback);
};
@@ -415,7 +421,7 @@ static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong pt
}
static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobjectArray waveform, jlong vibrationId) {
+ jobjectArray waveform, jlong vibrationId, jlong stepId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformPwleV2Effect failed because native wrapper was not initialized");
@@ -431,7 +437,7 @@ static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong
}
composite.pwlePrimitives = primitives;
- auto callback = wrapper->createCallback(vibrationId);
+ auto callback = wrapper->createCallback(vibrationId, stepId);
auto composePwleV2Fn = [&composite, &callback](vibrator::HalWrapper* hal) {
return hal->composePwleV2(composite, callback);
};
@@ -610,16 +616,16 @@ static const JNINativeMethod method_table[] = {
(void*)vibratorNativeInit},
{"getNativeFinalizer", "()J", (void*)vibratorGetNativeFinalizer},
{"isAvailable", "(J)Z", (void*)vibratorIsAvailable},
- {"on", "(JJJ)J", (void*)vibratorOn},
+ {"on", "(JJJJ)J", (void*)vibratorOn},
{"off", "(J)V", (void*)vibratorOff},
{"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
- {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
- {"performVendorEffect", "(JLandroid/os/Parcel;JFFJ)J", (void*)vibratorPerformVendorEffect},
- {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
+ {"performEffect", "(JJJJJ)J", (void*)vibratorPerformEffect},
+ {"performVendorEffect", "(JLandroid/os/Parcel;JFFJJ)J", (void*)vibratorPerformVendorEffect},
+ {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;JJ)J",
(void*)vibratorPerformComposedEffect},
- {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
+ {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJJ)J",
(void*)vibratorPerformPwleEffect},
- {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;J)J",
+ {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;JJ)J",
(void*)vibratorPerformPwleV2Effect},
{"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
{"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
@@ -632,7 +638,7 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
auto listenerClassName =
"com/android/server/vibrator/VibratorController$OnVibrationCompleteListener";
jclass listenerClass = FindClassOrDie(env, listenerClassName);
- sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V");
+ sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJJ)V");
jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment");
sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 191c21e661d0..aee32a0473a3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -423,6 +423,7 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.hardware.usb.UsbManager;
+import android.health.connect.HealthConnectManager;
import android.location.Location;
import android.location.LocationManager;
import android.media.AudioManager;
@@ -2149,6 +2150,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
mBackgroundHandler = BackgroundThread.getHandler();
+ // Add the health permission to the list of restricted permissions.
+ if (android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) {
+ Set<String> healthPermissions = HealthConnectManager.getHealthPermissions(mContext);
+ for (String permission : healthPermissions) {
+ SENSOR_PERMISSIONS.add(permission);
+ }
+ }
+
// Needed when mHasFeature == false, because it controls the certificate warning text.
mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index ae5e85163e9a..856466675a28 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -40,10 +40,14 @@ import android.content.res.Configuration;
import android.graphics.Insets;
import android.os.Build;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.server.wm.WindowManagerStateHelper;
import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
@@ -72,6 +76,7 @@ import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -139,10 +144,9 @@ public class InputMethodServiceTest {
if (!mOriginalVerboseImeTrackerLoggingEnabled) {
setVerboseImeTrackerLogging(true);
}
+ mUiDevice.setOrientationNatural();
prepareIme();
prepareActivity();
- mUiDevice.freezeRotation();
- mUiDevice.setOrientationNatural();
// Waits for input binding ready.
eventually(() -> {
mInputMethodService = InputMethodServiceWrapper.getInstance();
@@ -169,6 +173,9 @@ public class InputMethodServiceTest {
@After
public void tearDown() throws Exception {
+ if (!mUiDevice.isNaturalOrientation()) {
+ mUiDevice.setOrientationNatural();
+ }
mUiDevice.unfreezeRotation();
if (!mOriginalVerboseImeTrackerLoggingEnabled) {
setVerboseImeTrackerLogging(false);
@@ -245,6 +252,61 @@ public class InputMethodServiceTest {
}
/**
+ * This checks that the surface is removed after the window was hidden in
+ * InputMethodService#hideSoftInput
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testSurfaceRemovedAfterHideSoftInput() {
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ // Triggers to show IME via public API.
+ verifyInputViewStatusOnMainSync(() -> mActivity.showImeWithWindowInsetsController(),
+ EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+ assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+
+ final var window = mInputMethodService.getWindow().getWindow();
+ assertWithMessage("IME window exists").that(window).isNotNull();
+ assertWithMessage("IME window showing").that(
+ window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE);
+
+ mActivity.getWindow().getDecorView().setWindowInsetsAnimationCallback(
+ new WindowInsetsAnimation.Callback(
+ WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
+ @NonNull
+ @Override
+ public WindowInsetsAnimation.Bounds onStart(
+ @NonNull WindowInsetsAnimation animation,
+ @NonNull WindowInsetsAnimation.Bounds bounds) {
+ return super.onStart(animation, bounds);
+ }
+
+ @NonNull
+ @Override
+ public WindowInsets onProgress(@NonNull WindowInsets insets,
+ @NonNull List<WindowInsetsAnimation> runningAnimations) {
+ assertWithMessage("IME surface not removed during the animation").that(
+ window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE);
+ return insets;
+ }
+
+ @Override
+ public void onEnd(@NonNull WindowInsetsAnimation animation) {
+ assertWithMessage(
+ "IME surface not removed before the end of the animation").that(
+ window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE);
+ super.onEnd(animation);
+ }
+ });
+
+ // Triggers to hide IME via public API.
+ verifyInputViewStatusOnMainSync(() -> mActivity.hideImeWithWindowInsetsController(),
+ EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
+ eventually(() -> assertWithMessage("IME window not showing").that(
+ window.getDecorView().getVisibility()).isEqualTo(View.GONE));
+ }
+
+ /**
* This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
*/
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index e0e44252a8f3..c151732cec66 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -984,8 +984,7 @@ public class DisplayManagerServiceTest {
Handler handler = displayManager.getDisplayHandler();
waitForIdleHandler(handler);
- assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED,
- EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+ assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED);
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1ef758cf192e..340115a7d465 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -3337,6 +3337,108 @@ public class MockingOomAdjusterTests {
followUpTimeCaptor.capture());
}
+ /**
+ * For Perceptible Tasks adjustment, this solely unit-tests OomAdjuster -> onOtherActivity()
+ */
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS)
+ public void testPerceptibleAdjustment() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+
+ long now = mInjector.getUptimeMillis();
+
+ // GIVEN: perceptible adjustment is NOT enabled (perceptible stop time is not set)
+ // EXPECT: zero adjustment
+ // TLDR: App is not set as a perceptible task and hence no oom_adj boosting.
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+ false, false, PROCESS_STATE_CACHED_ACTIVITY,
+ SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(-1);
+ assertEquals(CACHED_APP_MIN_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+
+ // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and
+ // elapsed time < PERCEPTIBLE_TASK_TIMEOUT
+ // EXPECT: adjustment to PERCEPTIBLE_MEDIUM_APP_ADJ
+ // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting.
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+ false, false, PROCESS_STATE_CACHED_ACTIVITY,
+ SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mInjector.reset();
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(now);
+ assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ,
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+
+ // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and
+ // elapsed time > PERCEPTIBLE_TASK_TIMEOUT
+ // EXPECT: adjustment to PREVIOUS_APP_ADJ
+ // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting, but
+ // time has elapsed and has dropped to a lower boosting of PREVIOUS_APP_ADJ
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+ false, false, PROCESS_STATE_CACHED_ACTIVITY,
+ SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 1000);
+ mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(0);
+ assertEquals(PREVIOUS_APP_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+ }
+
+ /**
+ * For Perceptible Tasks adjustment, this tests overall adjustment flow.
+ */
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS)
+ public void testUpdateOomAdjPerceptible() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ WindowProcessController wpc = app.getWindowProcessController();
+
+ // Set uptime to be at least the timeout time + buffer, so that we don't end up with
+ // negative stopTime in our test input
+ mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 60L * 1000L);
+ long now = mInjector.getUptimeMillis();
+ doReturn(true).when(wpc).hasActivities();
+
+ // GIVEN: perceptible adjustment is is enabled
+ // EXPECT: perceptible-act adjustment
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+ .when(wpc).getActivityStateFlags();
+ doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_IMPORTANT_BACKGROUND, PERCEPTIBLE_MEDIUM_APP_ADJ,
+ SCHED_GROUP_BACKGROUND, "perceptible-act");
+
+ // GIVEN: perceptible adjustment is is enabled and timeout has been reached
+ // EXPECT: stale-perceptible-act adjustment
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+ .when(wpc).getActivityStateFlags();
+
+ doReturn(now - OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS).when(
+ wpc).getPerceptibleTaskStoppedTimeMillis();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ SCHED_GROUP_BACKGROUND, "stale-perceptible-act");
+
+ // GIVEN: perceptible adjustment is is disabled
+ // EXPECT: no perceptible adjustment
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+ .when(wpc).getActivityStateFlags();
+ doReturn(Long.MIN_VALUE).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_CACHED_ACTIVITY, CACHED_APP_MIN_ADJ,
+ SCHED_GROUP_BACKGROUND, "cch-act");
+
+ // GIVEN: perceptible app is in foreground
+ // EXPECT: no perceptible adjustment
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE)
+ .when(wpc).getActivityStateFlags();
+ doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT, "vis-activity");
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testUpdateOomAdj_DoAll_Multiple_Provider_Retention() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 7f60caaa569b..0745c68fd337 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.platform.test.annotations.DisableFlags;
@@ -400,6 +401,46 @@ public class AutoclickControllerTest {
.isNotEqualTo(initialScheduledTime);
}
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void pauseButton_flagOn_clickNotTriggeredWhenPaused() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Pause autoclick.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ // Send hover move event.
+ MotionEvent hoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+ // Verify there is not a pending click.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+
+ // Resume autoclick.
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(false);
+
+ // Send initial move event again. Because this is the first recorded move, a click won't be
+ // scheduled.
+ injectFakeMouseActionHoverMoveEvent();
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+
+ // Send move again to trigger click and verify there is now a pending click.
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+ }
+
private void injectFakeMouseActionHoverMoveEvent() {
MotionEvent event = getFakeMotionHoverMoveEvent();
event.setSource(InputDevice.SOURCE_MOUSE);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index f3016f4b17f9..ba672dcd299b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -68,6 +68,7 @@ public class AutoclickTypePanelTest {
private LinearLayout mPositionButton;
private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+ private boolean mPaused;
private final ClickPanelControllerInterface clickPanelController =
new ClickPanelControllerInterface() {
@@ -77,7 +78,9 @@ public class AutoclickTypePanelTest {
}
@Override
- public void toggleAutoclickPause() {}
+ public void toggleAutoclickPause(boolean paused) {
+ mPaused = paused;
+ }
};
@Before
@@ -199,6 +202,17 @@ public class AutoclickTypePanelTest {
}
}
+ @Test
+ public void pauseButton_onClick() {
+ mPauseButton.callOnClick();
+ assertThat(mPaused).isTrue();
+ assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+ mPauseButton.callOnClick();
+ assertThat(mPaused).isFalse();
+ assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+ }
+
private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
assertThat(gradientDrawable.getColor().getDefaultColor())
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index e5fac7ac5e0c..00b0c558b4e3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -263,6 +263,11 @@ public class DpmMockContext extends MockContext {
}
@Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mMockSystemServices.packageManager;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5dea44d6ebf4..67e85ff2445d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -4495,6 +4495,27 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void testBubblePreference_sameVersionWithSAWPermission() throws Exception {
+ when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+ anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+
+ final String xml = "<ranking version=\"4\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+ + "<channel id=\"someId\" name=\"hi\""
+ + " importance=\"3\"/>"
+ + "</package>"
+ + "</ranking>";
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O));
+ assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+ }
+
+ @Test
public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception {
when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index d5548a4f375e..f091a65cfe46 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -497,19 +497,19 @@ public class DeviceAdapterTest {
private VibratorController createEmptyVibratorController(int vibratorId) {
return new FakeVibratorControllerProvider(mTestLooper.getLooper())
- .newVibratorController(vibratorId, (id, vibrationId) -> {});
+ .newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private VibratorController createBasicVibratorController(int vibratorId) {
FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
IVibrator.CAP_COMPOSE_EFFECTS);
- return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private VibratorController createPwleWithoutFrequenciesVibratorController(int vibratorId) {
FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
- return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private VibratorController createPwleVibratorController(int vibratorId) {
@@ -519,7 +519,7 @@ public class DeviceAdapterTest {
provider.setMinFrequency(TEST_MIN_FREQUENCY);
provider.setFrequencyResolution(TEST_FREQUENCY_RESOLUTION);
provider.setMaxAmplitudes(TEST_AMPLITUDE_MAP);
- return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private VibratorController createPwleV2VibratorController(int vibratorId) {
@@ -538,7 +538,7 @@ public class DeviceAdapterTest {
provider.setMinEnvelopeEffectControlPointDurationMillis(
TEST_MIN_ENVELOPE_EFFECT_CONTROL_POINT_DURATION_MILLIS);
- return provider.newVibratorController(vibratorId, (id, vibrationId) -> {});
+ return provider.newVibratorController(vibratorId, (id, vibrationId, stepId) -> {});
}
private FakeVibratorControllerProvider createVibratorProviderWithEffects(int... capabilities) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 5f2af0a085c3..42279e40fa33 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -17,6 +17,9 @@
package com.android.server.vibrator;
import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -66,7 +69,6 @@ import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -153,9 +155,10 @@ public class VibrationThreadTest {
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
.thenReturn(new ComponentName("", ""));
doAnswer(answer -> {
- mVibrationConductor.notifyVibratorComplete(answer.getArgument(0));
+ mVibrationConductor.notifyVibratorComplete(
+ answer.getArgument(0), answer.getArgument(2));
return null;
- }).when(mControllerCallbacks).onComplete(anyInt(), anyLong());
+ }).when(mControllerCallbacks).onComplete(anyInt(), anyLong(), anyLong());
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
@@ -190,7 +193,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id));
+ verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
}
@@ -203,7 +206,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id));
+ verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
}
@@ -217,7 +220,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -234,7 +237,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -254,7 +257,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -265,7 +268,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
// No user settings scale.
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
@@ -291,7 +294,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
// No user settings scale.
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
@@ -412,6 +415,7 @@ public class VibrationThreadTest {
.isEqualTo(expectedOneShots(200L, 50L));
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@LargeTest
@Test
public void vibrate_singleVibratorRepeatingPatternWithZeroDurationSteps_repeatsEffectCorrectly()
@@ -443,6 +447,30 @@ public class VibrationThreadTest {
.isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L));
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
+ @Test
+ public void vibrate_singleVibratorPatternWithCallbackDelay_oldCallbacksIgnored() {
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+ fakeVibrator.setCompletionCallbackDelay(100); // 100ms delay to notify service.
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[]{0, 200, 50, 400}, /* repeat= */ -1);
+ HalVibration vibration = startThreadAndDispatcher(effect);
+ waitForCompletion(800 + TEST_TIMEOUT_MILLIS); // 200 + 50 + 400 + 100ms delay
+
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
+ assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), eq(1L));
+ // Step id = 2 skipped by the 50ms OFF step after the 200ms ON step.
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), eq(3L));
+
+ // First callback ignored, did not cause the vibrator to turn back on during the 400ms step.
+ assertThat(fakeVibrator.getEffectSegments(vibration.id))
+ .isEqualTo(expectedOneShots(200L, 400L));
+ }
+
@Test
public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -479,12 +507,12 @@ public class VibrationThreadTest {
throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK);
fakeVibrator.setCompositionSizeMax(10);
VibrationEffect effect = VibrationEffect.startComposition()
// Very long delay so thread will be cancelled after first PWLE is triggered.
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
.compose();
VibrationEffect repeatingEffect = VibrationEffect.startComposition()
.repeatEffectIndefinitely(effect)
@@ -561,13 +589,12 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
@@ -591,7 +618,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_singleVibratorVendorEffectCancel_cancelsVibrationImmediately()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -657,7 +684,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -678,7 +705,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -695,13 +722,14 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, never())
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_singleVibratorVendorEffect_runsVibration() {
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -712,7 +740,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID),
eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
@@ -725,24 +753,23 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorComposed_runsVibration() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_TICK, 0.5f)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
+ expectedPrimitive(PRIMITIVE_CLICK, 1, 0),
+ expectedPrimitive(PRIMITIVE_TICK, 0.5f, 0)),
fakeVibrator.getEffectSegments(vibration.id));
}
@@ -750,14 +777,15 @@ public class VibrationThreadTest {
@DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
public void vibrate_singleVibratorComposedAndNoCapability_triggersHalAndReturnsUnsupported() {
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, never())
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
}
@@ -766,14 +794,15 @@ public class VibrationThreadTest {
@EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, never())
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
}
@@ -782,34 +811,30 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- fakeVibrator.setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK,
- VibrationEffect.Composition.PRIMITIVE_SPIN);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_SPIN);
fakeVibrator.setCompositionSizeMax(2);
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_TICK, 0.5f)
+ .addPrimitive(PRIMITIVE_SPIN, 0.8f)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
verifyCallbacksTriggered(vibration, Status.FINISHED);
// Vibrator compose called twice.
- verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(2))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertEquals(3, fakeVibrator.getEffectSegments(vibration.id).size());
}
@Test
- @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- fakeVibrator.setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
fakeVibrator.setMinFrequency(100);
@@ -820,8 +845,8 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.startComposition()
.addEffect(VibrationEffect.createOneShot(10, 100))
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_TICK, 0.5f)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addEffect(VibrationEffect.startWaveform()
.addTransition(Duration.ofMillis(10),
@@ -836,13 +861,14 @@ public class VibrationThreadTest {
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(5))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedOneShot(10),
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
+ expectedPrimitive(PRIMITIVE_CLICK, 1, 0),
+ expectedPrimitive(PRIMITIVE_TICK, 0.5f, 0),
expectedPrebaked(VibrationEffect.EFFECT_CLICK),
expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
/* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
@@ -857,17 +883,15 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorComposedWithFallback_replacedInTheMiddleOfComposition() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- fakeVibrator.setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK);
+ fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
VibrationEffect effect = VibrationEffect.startComposition()
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(PRIMITIVE_CLICK, 1f)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .addPrimitive(PRIMITIVE_TICK, 0.5f)
.compose();
HalVibration vibration = createVibration(CombinedVibration.createParallel(effect));
vibration.fillFallbacks(unused -> fallback);
@@ -877,7 +901,8 @@ public class VibrationThreadTest {
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(4))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -894,7 +919,7 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_runsComposePwleV2() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -915,7 +940,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -929,7 +954,7 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorBasicPwle_runsComposePwleV2() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -951,7 +976,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(220L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -964,7 +989,7 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_withInitialFrequency_runsComposePwleV2() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -987,7 +1012,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -1001,7 +1026,7 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_TooManyControlPoints_splitsAndRunsComposePwleV2() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -1027,7 +1052,8 @@ public class VibrationThreadTest {
verifyCallbacksTriggered(vibration, Status.FINISHED);
// Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
// Using best split points instead of max-packing PWLEs.
- verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(3))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -1043,7 +1069,7 @@ public class VibrationThreadTest {
}
@Test
- @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_runsComposePwle() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
@@ -1066,7 +1092,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
@@ -1109,7 +1135,8 @@ public class VibrationThreadTest {
// Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
// Using best split points instead of max-packing PWLEs.
- verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks, times(3))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertEquals(6, fakeVibrator.getEffectSegments(vibration.id).size());
}
@@ -1160,8 +1187,8 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
- verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
@@ -1183,9 +1210,9 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
@@ -1207,11 +1234,10 @@ public class VibrationThreadTest {
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(4).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(4).setSupportedPrimitives(PRIMITIVE_CLICK);
VibrationEffect composed = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
@@ -1225,10 +1251,10 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
@@ -1243,8 +1269,7 @@ public class VibrationThreadTest {
assertEquals(Arrays.asList(expectedOneShot(20)),
mVibratorProviders.get(3).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
- assertEquals(Arrays.asList(
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(4).getEffectSegments(vibration.id));
}
@@ -1253,12 +1278,11 @@ public class VibrationThreadTest {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(2).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibrationEffect composed = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startSequential()
.addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 50)
@@ -1268,10 +1292,10 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- InOrder controllerVerifier = inOrder(mControllerCallbacks);
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
- controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
+ InOrder verifier = inOrder(mControllerCallbacks);
+ verifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
+ verifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+ verifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
InOrder batteryVerifier = inOrder(mManagerHooks);
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
@@ -1289,8 +1313,7 @@ public class VibrationThreadTest {
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(3).getEffectSegments(vibration.id));
@@ -1301,15 +1324,13 @@ public class VibrationThreadTest {
int[] vibratorIds = new int[]{1, 2};
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(1).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(1).setSupportedPrimitives(PRIMITIVE_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(2).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
VibrationEffect composed = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1, 100)
.compose();
CombinedVibration effect = CombinedVibration.createParallel(composed);
// We create the HalVibration here to obtain the vibration id and use it to mock the
@@ -1331,8 +1352,7 @@ public class VibrationThreadTest {
verify(mManagerHooks, never()).cancelSyncedVibration();
verifyCallbacksTriggered(vibration, Status.FINISHED);
- VibrationEffectSegment expected = expectedPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+ VibrationEffectSegment expected = expectedPrimitive(PRIMITIVE_CLICK, 1, 100);
assertEquals(Arrays.asList(expected),
mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expected),
@@ -1345,12 +1365,11 @@ public class VibrationThreadTest {
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(4).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(4).setSupportedPrimitives(PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
VibrationEffect composed = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
@@ -1463,9 +1482,9 @@ public class VibrationThreadTest {
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
- verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+ verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
@@ -1505,7 +1524,7 @@ public class VibrationThreadTest {
waitForCompletion(rampDownDuration + TEST_TIMEOUT_MILLIS);
long completionTime = SystemClock.elapsedRealtime();
- verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
// Vibration ends after duration, thread completed after ramp down
assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration);
assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + rampDownDuration);
@@ -1534,7 +1553,7 @@ public class VibrationThreadTest {
waitForCompletion(TEST_TIMEOUT_MILLIS);
- verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackDelay);
}
@@ -1563,7 +1582,8 @@ public class VibrationThreadTest {
waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS);
long completionTime = SystemClock.elapsedRealtime();
- verify(mControllerCallbacks, never()).onComplete(VIBRATOR_ID, vibration.id);
+ verify(mControllerCallbacks, never())
+ .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
// Vibration ends and thread completes after timeout, before the HAL callback
assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackTimeout);
assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + callbackDelay);
@@ -1639,15 +1659,14 @@ public class VibrationThreadTest {
mockVibrators(1, 2);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(2).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addVibrator(2, VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+ .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
.compose())
.combine();
HalVibration vibration = startThreadAndDispatcher(effect);
@@ -1672,7 +1691,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_multipleVendorEffectCancel_cancelsVibrationImmediately() throws Exception {
mockVibrators(1, 2);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -1767,7 +1786,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
// Duration extended for 5 + 5 + 5 + 15.
@@ -1847,7 +1866,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
@@ -1856,7 +1875,7 @@ public class VibrationThreadTest {
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -1865,7 +1884,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id))
@@ -1879,26 +1898,24 @@ public class VibrationThreadTest {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
- assertEquals(
- Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@Test
- @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -1916,7 +1933,7 @@ public class VibrationThreadTest {
HalVibration vibration = startThreadAndDispatcher(effect);
waitForCompletion();
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
@@ -1928,8 +1945,7 @@ public class VibrationThreadTest {
public void vibrate_multipleVibrations_withCancel() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(
VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
- mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1940,7 +1956,7 @@ public class VibrationThreadTest {
.repeatEffectIndefinitely(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.compose();
VibrationEffect effect3 = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_CLICK)
.compose();
VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100);
VibrationEffect effect5 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
@@ -1974,14 +1990,15 @@ public class VibrationThreadTest {
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
// Effect1
- verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration1.id);
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration1.id), anyLong());
verifyCallbacksTriggered(vibration1, Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
fakeVibrator.getEffectSegments(vibration1.id));
// Effect2: repeating, cancelled.
- verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibration2.id);
+ verify(mControllerCallbacks, atLeast(2))
+ .onComplete(eq(VIBRATOR_ID), eq(vibration2.id), anyLong());
verifyCallbacksTriggered(vibration2, Status.CANCELLED_BY_USER);
// The exact count of segments might vary, so just check that there's more than 2 and
@@ -1994,10 +2011,9 @@ public class VibrationThreadTest {
}
// Effect3
- verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id), anyLong());
verifyCallbacksTriggered(vibration3, Status.FINISHED);
- assertEquals(Arrays.asList(
- expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)),
fakeVibrator.getEffectSegments(vibration3.id));
// Effect4: cancelled quickly.
@@ -2014,8 +2030,7 @@ public class VibrationThreadTest {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(2).setSupportedPrimitives(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
+ mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
CombinedVibration effect = CombinedVibration.startSequential()
@@ -2029,8 +2044,7 @@ public class VibrationThreadTest {
/* delay= */ TEST_TIMEOUT_MILLIS)
.addNext(2,
VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1,
- /* delay= */ TEST_TIMEOUT_MILLIS)
+ .addPrimitive(PRIMITIVE_CLICK, 1, /* delay= */ TEST_TIMEOUT_MILLIS)
.compose(),
/* delay= */ TEST_TIMEOUT_MILLIS)
.combine();
@@ -2051,8 +2065,7 @@ public class VibrationThreadTest {
assertEquals(Arrays.asList(expectedOneShot(TEST_TIMEOUT_MILLIS)),
mVibratorProviders.get(1).getEffectSegments(vibration.id));
assertEquals(expectedAmplitudes(255), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(expectedPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)),
+ assertEquals(Arrays.asList(expectedPrimitive(PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)),
mVibratorProviders.get(2).getEffectSegments(vibration.id));
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(3).getEffectSegments(vibration.id));
@@ -2205,7 +2218,7 @@ public class VibrationThreadTest {
private void verifyCallbacksTriggered(HalVibration vibration, Status expectedStatus) {
assertThat(vibration.getStatus()).isEqualTo(expectedStatus);
- verify(mManagerHooks).onVibrationThreadReleased(vibration.id);
+ verify(mManagerHooks).onVibrationThreadReleased(eq(vibration.id));
}
private static final class TestLooperAutoDispatcher extends Thread {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index 0978f48491cc..4df13deaed7d 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -203,9 +203,10 @@ public class VibratorControllerTest {
@Test
public void setAmplitude_vibratorVibrating_setsAmplitude() {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
- controller.on(100, /* vibrationId= */ 1);
+ controller.on(100, 1, 1);
assertTrue(controller.isVibrating());
assertEquals(-1, controller.getCurrentAmplitude(), /* delta= */ 0);
@@ -215,81 +216,84 @@ public class VibratorControllerTest {
@Test
public void on_withDuration_turnsVibratorOn() {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
- controller.on(100, 10);
+ controller.on(100, 10, 20);
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).on(eq(100L), eq(10L));
+ verify(mNativeWrapperMock).on(eq(100L), eq(10L), eq(20L));
}
@Test
public void on_withPrebaked_performsEffect() {
- when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L);
+ when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong(), anyLong()))
+ .thenReturn(10L);
VibratorController controller = createController();
PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_STRENGTH_MEDIUM);
- assertEquals(10L, controller.on(prebaked, 11));
+ assertEquals(10L, controller.on(prebaked, 11, 23));
assertTrue(controller.isVibrating());
verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK),
- eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L));
+ eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L), eq(23L));
}
@Test
public void on_withComposed_performsEffect() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L);
+ when(mNativeWrapperMock.compose(any(), anyLong(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
PrimitiveSegment[] primitives = new PrimitiveSegment[]{
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
};
- assertEquals(15L, controller.on(primitives, 12));
+ assertEquals(15L, controller.on(primitives, 12, 34));
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
+ verify(mNativeWrapperMock).compose(eq(primitives), eq(12L), eq(34L));
}
@Test
public void on_withComposedPwle_performsEffect() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
- when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong())).thenReturn(15L);
+ when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
RampSegment[] primitives = new RampSegment[]{
new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
/* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10)
};
- assertEquals(15L, controller.on(primitives, 12));
+ assertEquals(15L, controller.on(primitives, 12, 45));
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L));
+ verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L), eq(45L));
}
@Test
public void on_withComposedPwleV2_performsEffect() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
- when(mNativeWrapperMock.composePwleV2(any(), anyLong())).thenReturn(15L);
+ when(mNativeWrapperMock.composePwleV2(any(), anyLong(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
PwlePoint[] primitives = new PwlePoint[]{
new PwlePoint(/*amplitude=*/ 0, /*frequencyHz=*/ 100, /*timeMillis=*/ 0),
new PwlePoint(/*amplitude=*/ 1, /*frequencyHz=*/ 200, /*timeMillis=*/ 10)
};
- assertEquals(15L, controller.on(primitives, 12));
+ assertEquals(15L, controller.on(primitives, 12, 53));
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L));
+ verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L), eq(53L));
}
@Test
public void off_turnsOffVibrator() {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
- controller.on(100, 1);
+ controller.on(100, 1, 1);
assertTrue(controller.isVibrating());
controller.off();
@@ -301,10 +305,11 @@ public class VibratorControllerTest {
@Test
public void reset_turnsOffVibratorAndDisablesExternalControl() {
mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
- controller.on(100, 1);
+ controller.on(100, 1, 1);
assertTrue(controller.isVibrating());
controller.reset();
@@ -315,12 +320,13 @@ public class VibratorControllerTest {
@Test
public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
controller.registerVibratorStateListener(mVibratorStateListenerMock);
- controller.on(10, 1);
- controller.on(100, 2);
+ controller.on(10, 1, 1);
+ controller.on(100, 2, 1);
controller.off();
controller.off();
@@ -334,19 +340,20 @@ public class VibratorControllerTest {
@Test
public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
- when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+ .thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
controller.registerVibratorStateListener(mVibratorStateListenerMock);
verify(mVibratorStateListenerMock).onVibrating(false);
- controller.on(10, 1);
+ controller.on(10, 1, 1);
verify(mVibratorStateListenerMock).onVibrating(true);
controller.unregisterVibratorStateListener(mVibratorStateListenerMock);
Mockito.clearInvocations(mVibratorStateListenerMock);
- controller.on(10, 1);
+ controller.on(10, 1, 1);
verifyNoMoreInteractions(mVibratorStateListenerMock);
}
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 3f3476716831..bd806b7f7f37 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -118,11 +118,11 @@ public final class FakeVibratorControllerProvider {
}
@Override
- public long on(long milliseconds, long vibrationId) {
+ public long on(long milliseconds, long vibrationId, long stepId) {
recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0, (int) milliseconds));
applyLatency(mOnLatency);
- scheduleListener(milliseconds, vibrationId);
+ scheduleListener(milliseconds, vibrationId, stepId);
return milliseconds;
}
@@ -139,7 +139,7 @@ public final class FakeVibratorControllerProvider {
}
@Override
- public long perform(long effect, long strength, long vibrationId) {
+ public long perform(long effect, long strength, long vibrationId, long stepId) {
if (mSupportedEffects == null
|| Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
return 0;
@@ -147,13 +147,13 @@ public final class FakeVibratorControllerProvider {
recordEffectSegment(vibrationId,
new PrebakedSegment((int) effect, false, (int) strength));
applyLatency(mOnLatency);
- scheduleListener(EFFECT_DURATION, vibrationId);
+ scheduleListener(EFFECT_DURATION, vibrationId, stepId);
return EFFECT_DURATION;
}
@Override
public long performVendorEffect(Parcel vendorData, long strength, float scale,
- float adaptiveScale, long vibrationId) {
+ float adaptiveScale, long vibrationId, long stepId) {
if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) {
return 0;
}
@@ -161,13 +161,13 @@ public final class FakeVibratorControllerProvider {
recordVendorEffect(vibrationId,
new VibrationEffect.VendorEffect(bundle, (int) strength, scale, adaptiveScale));
applyLatency(mOnLatency);
- scheduleListener(mVendorEffectDuration, vibrationId);
+ scheduleListener(mVendorEffectDuration, vibrationId, stepId);
// HAL has unknown duration for vendor effects.
return Long.MAX_VALUE;
}
@Override
- public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+ public long compose(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
if (mSupportedPrimitives == null) {
return 0;
}
@@ -182,12 +182,13 @@ public final class FakeVibratorControllerProvider {
recordEffectSegment(vibrationId, primitive);
}
applyLatency(mOnLatency);
- scheduleListener(duration, vibrationId);
+ scheduleListener(duration, vibrationId, stepId);
return duration;
}
@Override
- public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
+ public long composePwle(RampSegment[] primitives, int braking, long vibrationId,
+ long stepId) {
long duration = 0;
for (RampSegment primitive : primitives) {
duration += primitive.getDuration();
@@ -195,19 +196,19 @@ public final class FakeVibratorControllerProvider {
}
recordBraking(vibrationId, braking);
applyLatency(mOnLatency);
- scheduleListener(duration, vibrationId);
+ scheduleListener(duration, vibrationId, stepId);
return duration;
}
@Override
- public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
+ public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
long duration = 0;
for (PwlePoint pwlePoint: pwlePoints) {
duration += pwlePoint.getTimeMillis();
recordEffectPwlePoint(vibrationId, pwlePoint);
}
applyLatency(mOnLatency);
- scheduleListener(duration, vibrationId);
+ scheduleListener(duration, vibrationId, stepId);
return duration;
}
@@ -263,8 +264,8 @@ public final class FakeVibratorControllerProvider {
}
}
- private void scheduleListener(long vibrationDuration, long vibrationId) {
- mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId),
+ private void scheduleListener(long vibrationDuration, long vibrationId, long stepId) {
+ mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId, stepId),
vibrationDuration + mCompletionCallbackDelay);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index d53ba1d24d8c..301a7544227f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -56,8 +56,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
@@ -921,12 +919,6 @@ public class ActivityRecordTests extends WindowTestsBase {
// animation and AR#takeSceneTransitionInfo also clear the AR#mPendingOptions
assertNull(activity.takeSceneTransitionInfo());
assertNull(activity.getOptions());
-
- final AppTransition appTransition = activity.mDisplayContent.mAppTransition;
- spyOn(appTransition);
- activity.applyOptionsAnimation();
-
- verify(appTransition).overridePendingAppTransitionRemote(any());
}
@Test
@@ -1190,7 +1182,6 @@ public class ActivityRecordTests extends WindowTestsBase {
FINISH_RESULT_REQUESTED, activity.finishIfPossible("test", false /* oomAdj */));
assertEquals(PAUSING, activity.getState());
verify(activity).setVisibility(eq(false));
- verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
}
/**
@@ -1237,7 +1228,6 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishIfPossible("test", false /* oomAdj */);
verify(activity).setVisibility(eq(false));
- verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
verify(activity.mDisplayContent, never()).executeAppTransition();
}
@@ -1254,7 +1244,6 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishIfPossible("test", false /* oomAdj */);
verify(activity, atLeast(1)).setVisibility(eq(false));
- verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
verify(activity.mDisplayContent).executeAppTransition();
}
@@ -1275,7 +1264,6 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishIfPossible("test", false /* oomAdj */);
- verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
assertFalse(activity.inTransition());
// finishIfPossible -> completeFinishing -> addToFinishingAndWaitForIdle
@@ -2657,10 +2645,6 @@ public class ActivityRecordTests extends WindowTestsBase {
@Presubmit
public void testGetOrientation() {
mDisplayContent.setIgnoreOrientationRequest(false);
- // ActivityBuilder will resume top activities and cause the activity been added into
- // opening apps list. Since this test is focus on the effect of visible on getting
- // orientation, we skip app transition to avoid interference.
- doNothing().when(mDisplayContent).prepareAppTransition(anyInt());
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.setVisible(true);
@@ -2925,7 +2909,6 @@ public class ActivityRecordTests extends WindowTestsBase {
sources.add(activity2);
doReturn(true).when(activity2).okToAnimate();
doReturn(true).when(activity2).isAnimating();
- assertTrue(activity2.applyAnimation(null, TRANSIT_OLD_ACTIVITY_OPEN, true, false, sources));
}
@Test
public void testTrackingStartingWindowThroughTrampoline() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d5b9751b0f51..9c483846217b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -63,9 +63,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -1742,8 +1739,6 @@ public class DisplayContentTests extends WindowTestsBase {
public void testFixedRotationWithPip() {
final DisplayContent displayContent = mDefaultDisplay;
displayContent.setIgnoreOrientationRequest(false);
- // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
- doNothing().when(displayContent).prepareAppTransition(anyInt());
// Make resume-top really update the activity state.
setBooted(mAtm);
clearInvocations(mWm);
@@ -1826,7 +1821,7 @@ public class DisplayContentTests extends WindowTestsBase {
.setTask(nonTopVisible.getTask()).setVisible(false)
.setActivityTheme(android.R.style.Theme_Translucent).build();
final TestTransitionPlayer player = registerTestTransitionPlayer();
- mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0, null);
translucentTop.setVisibility(true);
mDisplayContent.updateOrientation();
assertEquals("Non-top visible activity must be portrait",
@@ -2373,33 +2368,6 @@ public class DisplayContentTests extends WindowTestsBase {
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
- public void testShowImeScreenshot() {
- final Task rootTask = createTask(mDisplayContent);
- final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
- final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
- final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
- activity).build();
- task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
- doReturn(true).when(task).okToAnimate();
- ArrayList<WindowContainer> sources = new ArrayList<>();
- sources.add(activity);
-
- mDisplayContent.setImeLayeringTarget(win);
- spyOn(mDisplayContent);
-
- // Expecting the IME screenshot only be attached when performing task closing transition.
- task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
- false /* isVoiceInteraction */, sources);
- verify(mDisplayContent).showImeScreenshot();
-
- clearInvocations(mDisplayContent);
- activity.applyAnimation(null, TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, false /* enter */,
- false /* isVoiceInteraction */, sources);
- verify(mDisplayContent, never()).showImeScreenshot();
- }
-
- @SetupWindows(addWindows = W_INPUT_METHOD)
- @Test
public void testShowImeScreenshot_removeCurSnapshotBeforeCreateNext() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -2427,32 +2395,6 @@ public class DisplayContentTests extends WindowTestsBase {
@UseTestDisplay(addWindows = {W_INPUT_METHOD})
@Test
- public void testRemoveImeScreenshot_whenTargetSurfaceWasInvisible() {
- final Task rootTask = createTask(mDisplayContent);
- final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
- final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
- final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
- activity).build();
- win.onSurfaceShownChanged(true);
- makeWindowVisible(win, mDisplayContent.mInputMethodWindow);
- task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
- doReturn(true).when(task).okToAnimate();
- ArrayList<WindowContainer> sources = new ArrayList<>();
- sources.add(activity);
-
- mDisplayContent.setImeLayeringTarget(win);
- mDisplayContent.setImeInputTarget(win);
- mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
- task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
- false /* isVoiceInteraction */, sources);
- assertNotNull(mDisplayContent.mImeScreenshot);
-
- win.onSurfaceShownChanged(false);
- assertNull(mDisplayContent.mImeScreenshot);
- }
-
- @UseTestDisplay(addWindows = {W_INPUT_METHOD})
- @Test
public void testRemoveImeScreenshot_whenWindowRemoveImmediately() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
index db90c28ec7df..2d4101e40615 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -17,17 +17,21 @@
package com.android.server.wm;
import static android.view.Display.FLAG_PRESENTATION;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
+import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.UserHandle;
-import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
@@ -41,6 +45,7 @@ import android.view.WindowManagerGlobal;
import androidx.test.filters.SmallTest;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,35 +58,100 @@ import org.junit.runner.RunWith;
@RunWith(WindowTestRunner.class)
public class PresentationControllerTests extends WindowTestsBase {
+ TestTransitionPlayer mPlayer;
+
+ @Before
+ public void setUp() {
+ mPlayer = registerTestTransitionPlayer();
+ }
+
@EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
@Test
- public void testPresentationHidesActivitiesBehind() {
- final DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.copyFrom(mDisplayInfo);
- displayInfo.flags = FLAG_PRESENTATION;
- final DisplayContent dc = createNewDisplay(displayInfo);
- final int displayId = dc.getDisplayId();
- doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+ public void testPresentationShowAndHide() {
+ final DisplayContent dc = createPresentationDisplay();
final ActivityRecord activity = createActivityRecord(createTask(dc));
assertTrue(activity.isVisible());
- doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
- final int uid = 100000; // uid for non-system user
+ // Add a presentation window, which requests the activity to stop.
+ final WindowState window = addPresentationWindow(100000, dc.mDisplayId);
+ assertFalse(activity.isVisibleRequested());
+ assertTrue(activity.isVisible());
+ final Transition addTransition = window.mTransitionController.getCollectingTransition();
+ assertEquals(TRANSIT_OPEN, addTransition.mType);
+ assertTrue(addTransition.isInTransition(window));
+ assertTrue(addTransition.isInTransition(activity));
+
+ // Completing the transition makes the activity invisible.
+ completeTransition(addTransition, /*abortSync=*/ true);
+ assertFalse(activity.isVisible());
+
+ // Remove a Presentation window, which requests the activity to be resumed back.
+ window.removeIfPossible();
+ final Transition removeTransition = window.mTransitionController.getCollectingTransition();
+ assertEquals(TRANSIT_CLOSE, removeTransition.mType);
+ assertTrue(removeTransition.isInTransition(window));
+ assertTrue(removeTransition.isInTransition(activity));
+ assertTrue(activity.isVisibleRequested());
+ assertFalse(activity.isVisible());
+
+ // Completing the transition makes the activity visible.
+ completeTransition(removeTransition, /*abortSync=*/ false);
+ assertTrue(activity.isVisible());
+ }
+
+ @DisableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
+ @Test
+ public void testPresentationShowAndHide_flagDisabled() {
+ final DisplayContent dc = createPresentationDisplay();
+ final ActivityRecord activity = createActivityRecord(createTask(dc));
+ assertTrue(activity.isVisible());
+
+ final WindowState window = addPresentationWindow(100000, dc.mDisplayId);
+ assertFalse(window.mTransitionController.isCollecting());
+ assertTrue(activity.isVisibleRequested());
+ assertTrue(activity.isVisible());
+
+ window.removeIfPossible();
+ assertFalse(window.mTransitionController.isCollecting());
+ assertTrue(activity.isVisibleRequested());
+ assertTrue(activity.isVisible());
+ assertFalse(window.isAttached());
+ }
+
+ private WindowState addPresentationWindow(int uid, int displayId) {
final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
final int userId = UserHandle.getUserId(uid);
- doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
+ doReturn(true).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_PRESENTATION);
-
final IWindow clientWindow = new TestIWindow();
- final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
+ final int res = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
new InsetsSourceControl.Array(), new Rect(), new float[1]);
- assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
- assertFalse(activity.isVisible());
-
+ assertTrue(res >= WindowManagerGlobal.ADD_OKAY);
final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
- window.removeImmediately();
- assertTrue(activity.isVisible());
+ window.mHasSurface = true;
+ return window;
+ }
+
+ private DisplayContent createPresentationDisplay() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.flags = FLAG_PRESENTATION;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ final int displayId = dc.getDisplayId();
+ doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+ return dc;
+ }
+
+ private void completeTransition(@NonNull Transition transition, boolean abortSync) {
+ final ActionChain chain = ActionChain.testFinish(transition);
+ if (abortSync) {
+ // Forcefully finishing the active sync for testing purpose.
+ mWm.mSyncEngine.abort(transition.getSyncId());
+ } else {
+ transition.onTransactionReady(transition.getSyncId(), mTransaction);
+ }
+ transition.finishTransition(chain);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 9406779c929d..3776b03695d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -264,7 +264,7 @@ public class SystemServicesTestRule implements TestRule {
final DisplayManagerGlobal dmg = DisplayManagerGlobal.getInstance();
spyOn(dmg);
doNothing().when(dmg).registerDisplayListener(
- any(), any(Executor.class), anyLong(), anyString());
+ any(), any(Executor.class), anyLong(), anyString(), anyBoolean());
doNothing().when(dmg).registerTopologyListener(any(Executor.class), any(), anyString());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 2ee34d3a4b36..77ad7f7bac7f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -683,7 +683,7 @@ public class TransitionTests extends WindowTestsBase {
app.onStartingWindowDrawn();
// The task appeared event should be deferred until transition ready.
assertFalse(task.taskAppearedReady());
- testPlayer.onTransactionReady(app.getSyncTransaction());
+ testPlayer.onTransactionReady();
assertTrue(task.taskAppearedReady());
assertTrue(playerProc.isRunningRemoteTransition());
assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
@@ -1162,7 +1162,8 @@ public class TransitionTests extends WindowTestsBase {
screenDecor.updateSurfacePosition(mMockT);
assertEquals(prevPos, screenDecor.mLastSurfacePosition);
- final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction startTransaction = mTransaction;
+ clearInvocations(startTransaction);
final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
onRotationTransactionReady(player, startTransaction);
@@ -1213,7 +1214,8 @@ public class TransitionTests extends WindowTestsBase {
assertFalse(statusBar.mToken.inTransition());
assertTrue(app.getTask().inTransition());
- final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction startTransaction = mTransaction;
+ clearInvocations(startTransaction);
final SurfaceControl leash = statusBar.mToken.getAnimationLeash();
doReturn(true).when(leash).isValid();
final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
@@ -1287,7 +1289,8 @@ public class TransitionTests extends WindowTestsBase {
// Avoid DeviceStateController disturbing the test by triggering another rotation change.
doReturn(false).when(mDisplayContent).updateRotationUnchecked();
- onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted();
+ clearInvocations(mTransaction);
+ onRotationTransactionReady(player, mTransaction).onTransactionCommitted();
assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange(
mDisplayContent.mRemoteToken.toWindowContainerToken()).getRotationAnimation());
spyOn(navBarInsetsProvider);
@@ -1350,7 +1353,7 @@ public class TransitionTests extends WindowTestsBase {
mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
doReturn(true).when(home).hasFixedRotationTransform(any());
player.startTransition();
- player.onTransactionReady(mDisplayContent.getSyncTransaction());
+ player.onTransactionReady();
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
final RemoteDisplayChangeController displayChangeController = mDisplayContent
@@ -3071,8 +3074,11 @@ public class TransitionTests extends WindowTestsBase {
TestTransitionPlayer player, SurfaceControl.Transaction startTransaction) {
final ArgumentCaptor<SurfaceControl.TransactionCommittedListener> listenerCaptor =
ArgumentCaptor.forClass(SurfaceControl.TransactionCommittedListener.class);
- player.onTransactionReady(startTransaction);
- verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
+ player.onTransactionReady();
+ // The startTransaction is from mWm.mTransactionFactory.get() in SyncGroup#finishNow.
+ // 2 times are from SyncGroup#finishNow and AsyncRotationController#setupStartTransaction.
+ verify(startTransaction, times(2)).addTransactionCommittedListener(
+ any(), listenerCaptor.capture());
return listenerCaptor.getValue();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index cee98fb1b34c..4f310de1e48b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -30,8 +30,6 @@ import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemOverlays;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -1121,7 +1119,6 @@ public class WindowContainerTests extends WindowTestsBase {
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
activity).build();
- task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
spyOn(win);
doReturn(true).when(task).okToAnimate();
ArrayList<WindowContainer> sources = new ArrayList<>();
@@ -1130,8 +1127,6 @@ public class WindowContainerTests extends WindowTestsBase {
// Simulate the task applying the exit transition, verify the main window of the task
// will be set the frozen insets state before the animation starts
activity.setVisibility(false);
- task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
- false /* isVoiceInteraction */, sources);
verify(win).freezeInsetsState();
// Simulate the task transition finished.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 97c6ac6854c3..c6416850c464 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -2159,13 +2159,14 @@ public class WindowTestsBase extends SystemServiceTestsBase {
mOrganizer.startTransition(mLastTransit.getToken(), null);
}
- void onTransactionReady(SurfaceControl.Transaction t) {
- mLastTransit.onTransactionReady(mLastTransit.getSyncId(), t);
+ void onTransactionReady() {
+ // SyncGroup#finishNow -> Transition#onTransactionReady.
+ mController.mSyncEngine.abort(mLastTransit.getSyncId());
}
void start() {
startTransition();
- onTransactionReady(mock(SurfaceControl.Transaction.class));
+ onTransactionReady();
}
public void finish() {
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt
new file mode 100644
index 000000000000..c82ce8a4cb1d
--- /dev/null
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.transitions.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.transitions.scenarios.LaunchTask
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class LaunchTaskPortrait : LaunchTask(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["TASK_TRANSITION_SCENARIO", "OPEN_NEW_TASK_APP_SCENARIO"])
+ @Test
+ override fun openNewTask() = super.openNewTask()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(TASK_TRANSITION_SCENARIO)
+ .use(OPEN_NEW_TASK_APP_SCENARIO)
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt
new file mode 100644
index 000000000000..147477d728be
--- /dev/null
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.transitions.scenarios
+
+import android.app.Instrumentation
+import android.tools.Rotation
+import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.assertors.assertions.AppWindowCoversFullScreenAtStart
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.flicker.assertors.assertions.BackgroundShowsInTransition
+import android.tools.flicker.assertors.assertions.LayerBecomesInvisible
+import android.tools.flicker.assertors.assertions.LayerBecomesVisible
+import android.tools.flicker.assertors.assertions.LayerIsNeverVisible
+import android.tools.flicker.assertors.assertions.AppWindowIsNeverVisible
+import android.tools.flicker.config.AssertionTemplates
+import android.tools.flicker.config.FlickerConfigEntry
+import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.appclose.Components.CLOSING_APPS
+import android.tools.flicker.config.appclose.Components.CLOSING_CHANGES
+import android.tools.flicker.config.applaunch.Components.OPENING_CHANGES
+import android.tools.flicker.config.common.Components.LAUNCHER
+import android.tools.flicker.config.common.Components.WALLPAPER
+import android.tools.flicker.extractors.TaggedCujTransitionMatcher
+import android.tools.flicker.extractors.TaggedScenarioExtractorBuilder
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.events.CujType
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+
+/**
+ * This tests performs a transition between tasks
+ */
+abstract class LaunchTask(val rotation: Rotation = Rotation.ROTATION_0) {
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val tapl = LauncherInstrumentation()
+
+ private val launchNewTaskApp = NewTasksAppHelper(instrumentation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ tapl.setExpectedRotation(rotation.value)
+ launchNewTaskApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun openNewTask() {
+ launchNewTaskApp.openNewTask(device, wmHelper)
+ }
+
+ @After
+ fun tearDown() {
+ launchNewTaskApp.exit(wmHelper)
+ }
+
+ companion object {
+ /**
+ * General task transition scenario that can be reused for any trace
+ */
+ val TASK_TRANSITION_SCENARIO =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("TASK_TRANSITION_SCENARIO"),
+ extractor = TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = true)
+ )
+ .build(),
+ assertions = listOf(
+ // Opening changes replace the closing ones
+ LayerBecomesInvisible(CLOSING_CHANGES),
+ AppWindowOnTopAtStart(CLOSING_CHANGES),
+ LayerBecomesVisible(OPENING_CHANGES),
+ AppWindowOnTopAtEnd(OPENING_CHANGES),
+
+ // There is a background color and it's covering the transition area
+ BackgroundShowsInTransition(CLOSING_CHANGES)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
+
+ /**
+ * Scenario that is making assertions that are valid for the new task app but that do not
+ * apply to other task transitions in general
+ */
+ val OPEN_NEW_TASK_APP_SCENARIO =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("OPEN_NEW_TASK_APP_SCENARIO"),
+ extractor = TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = true)
+ )
+ .build(),
+ assertions = AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ // Wallpaper and launcher never visible
+ LayerIsNeverVisible(WALLPAPER, mustExist = true),
+ LayerIsNeverVisible(LAUNCHER, mustExist = true),
+ AppWindowIsNeverVisible(LAUNCHER, mustExist = true),
+ // App window covers the display at start
+ AppWindowCoversFullScreenAtStart(CLOSING_APPS)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
+ }
+} \ No newline at end of file
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 7a19add1d1b9..8cd89ce89e83 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -34,6 +34,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.Objects;
@@ -73,15 +74,21 @@ public class TestableLooper {
/**
* Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
*/
- private static boolean newTestabilityApisSupported() {
- return android.os.Flags.messageQueueTestability();
+ private static boolean isAtLeastBaklava() {
+ Method[] methods = TestLooperManager.class.getMethods();
+ for (Method method : methods) {
+ if (method.getName().equals("peekWhen")) {
+ return true;
+ }
+ }
+ return false;
// TODO(shayba): delete the above, uncomment the below.
// SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
// return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
}
static {
- if (newTestabilityApisSupported()) {
+ if (isAtLeastBaklava()) {
MESSAGE_QUEUE_MESSAGES_FIELD = null;
MESSAGE_NEXT_FIELD = null;
MESSAGE_WHEN_FIELD = null;
@@ -241,14 +248,14 @@ public class TestableLooper {
}
public void moveTimeForward(long milliSeconds) {
- if (newTestabilityApisSupported()) {
- moveTimeForwardModern(milliSeconds);
+ if (isAtLeastBaklava()) {
+ moveTimeForwardBaklava(milliSeconds);
} else {
moveTimeForwardLegacy(milliSeconds);
}
}
- private void moveTimeForwardModern(long milliSeconds) {
+ private void moveTimeForwardBaklava(long milliSeconds) {
// Drain all Messages from the queue.
Queue<Message> messages = new ArrayDeque<>();
while (true) {
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index c7a36dd3f9d8..4d379e45a81a 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -65,13 +65,19 @@ public class TestLooper {
private AutoDispatchThread mAutoDispatchThread;
/**
- * Modern introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
+ * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
*/
- private static boolean newTestabilityApisSupported() {
- return android.os.Flags.messageQueueTestability();
+ private static boolean isAtLeastBaklava() {
+ Method[] methods = TestLooperManager.class.getMethods();
+ for (Method method : methods) {
+ if (method.getName().equals("peekWhen")) {
+ return true;
+ }
+ }
+ return false;
// TODO(shayba): delete the above, uncomment the below.
- // SDK_INT has not yet ramped to Modern in all 25Q2 builds.
- // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Modern;
+ // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
+ // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
}
static {
@@ -81,7 +87,7 @@ public class TestLooper {
THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
- if (newTestabilityApisSupported()) {
+ if (isAtLeastBaklava()) {
MESSAGE_QUEUE_MESSAGES_FIELD = null;
MESSAGE_NEXT_FIELD = null;
MESSAGE_WHEN_FIELD = null;
@@ -130,7 +136,7 @@ public class TestLooper {
throw new RuntimeException("Reflection error constructing or accessing looper", e);
}
- if (newTestabilityApisSupported()) {
+ if (isAtLeastBaklava()) {
mTestLooperManager =
InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper);
} else {
@@ -159,14 +165,14 @@ public class TestLooper {
}
public void moveTimeForward(long milliSeconds) {
- if (newTestabilityApisSupported()) {
- moveTimeForwardModern(milliSeconds);
+ if (isAtLeastBaklava()) {
+ moveTimeForwardBaklava(milliSeconds);
} else {
moveTimeForwardLegacy(milliSeconds);
}
}
- private void moveTimeForwardModern(long milliSeconds) {
+ private void moveTimeForwardBaklava(long milliSeconds) {
// Drain all Messages from the queue.
Queue<Message> messages = new ArrayDeque<>();
while (true) {
@@ -259,14 +265,14 @@ public class TestLooper {
* @return true if there are pending messages in the message queue
*/
public boolean isIdle() {
- if (newTestabilityApisSupported()) {
- return isIdleModern();
+ if (isAtLeastBaklava()) {
+ return isIdleBaklava();
} else {
return isIdleLegacy();
}
}
- private boolean isIdleModern() {
+ private boolean isIdleBaklava() {
Long when = mTestLooperManager.peekWhen();
return when != null && currentTime() >= when;
}
@@ -280,14 +286,14 @@ public class TestLooper {
* @return the next message in the Looper's message queue or null if there is none
*/
public Message nextMessage() {
- if (newTestabilityApisSupported()) {
- return nextMessageModern();
+ if (isAtLeastBaklava()) {
+ return nextMessageBaklava();
} else {
return nextMessageLegacy();
}
}
- private Message nextMessageModern() {
+ private Message nextMessageBaklava() {
if (isIdle()) {
return mTestLooperManager.poll();
} else {
@@ -308,14 +314,14 @@ public class TestLooper {
* Asserts that there is a message in the queue
*/
public void dispatchNext() {
- if (newTestabilityApisSupported()) {
- dispatchNextModern();
+ if (isAtLeastBaklava()) {
+ dispatchNextBaklava();
} else {
dispatchNextLegacy();
}
}
- private void dispatchNextModern() {
+ private void dispatchNextBaklava() {
assertTrue(isIdle());
Message msg = mTestLooperManager.poll();
if (msg == null) {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index ff4d8ef2ec25..0a5cb1ff4956 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -2649,6 +2649,10 @@ int LinkCommand::Action(const std::vector<std::string>& args) {
".mpg", ".mpeg", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".wma", ".wmv",
".webm", ".mkv"});
+ if (options_.no_compress_fonts) {
+ options_.extensions_to_not_compress.insert({".ttf", ".otf", ".ttc"});
+ }
+
// Turn off auto versioning for static-libs.
if (context.GetPackageType() == PackageType::kStaticLib) {
options_.no_auto_version = true;
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 2f17853718ec..977978834fcd 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -78,6 +78,7 @@ struct LinkOptions {
bool use_sparse_encoding = false;
std::unordered_set<std::string> extensions_to_not_compress;
std::optional<std::regex> regex_to_not_compress;
+ bool no_compress_fonts = false;
FeatureFlagValues feature_flag_values;
// Static lib options.
@@ -300,6 +301,14 @@ class LinkCommand : public Command {
"use the '$' symbol for end of line. Uses a case-sensitive ECMAScript"
"regular expression grammar.",
&no_compress_regex);
+ AddOptionalSwitch("--no-compress-fonts",
+ "Do not compress files with common extensions for fonts.\n"
+ "This allows loading fonts directly from the APK, without needing to\n"
+ "decompress them first. Loading fonts will be faster and use less memory.\n"
+ "The downside is that the APK will be larger.\n"
+ "Passing this flag is functionally equivalent to passing the following flags:\n"
+ "-0 .ttf -0 .otf -0 .ttc",
+ &options_.no_compress_fonts);
AddOptionalSwitch("--warn-manifest-validation",
"Treat manifest validation errors as warnings.",
&options_.manifest_fixer_options.warn_validation);
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index a2dc8f8ce0fd..41f8e250efd7 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -98,6 +98,7 @@ TEST_F(LinkTest, NoCompressAssets) {
WriteFile(GetTestPath("assets/test.txt"), content);
WriteFile(GetTestPath("assets/test.hello.txt"), content);
WriteFile(GetTestPath("assets/test.hello.xml"), content);
+ WriteFile(GetTestPath("assets/fonts/myfont.ttf"), content);
const std::string out_apk = GetTestPath("out.apk");
std::vector<std::string> link_args = {
@@ -136,6 +137,10 @@ TEST_F(LinkTest, NoCompressAssets) {
file = zip->FindFile("assets/test.hello.xml");
ASSERT_THAT(file, Ne(nullptr));
EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/fonts/myfont.ttf");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_TRUE(file->WasCompressed());
}
TEST_F(LinkTest, NoCompressResources) {
@@ -182,6 +187,42 @@ TEST_F(LinkTest, NoCompressResources) {
EXPECT_FALSE(file->WasCompressed());
}
+TEST_F(LinkTest, NoCompressFonts) {
+ StdErrDiagnostics diag;
+ std::string content(500, 'a');
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test.txt"), content, compiled_files_dir, &diag));
+ WriteFile(GetTestPath("assets/fonts/myfont1.ttf"), content);
+ WriteFile(GetTestPath("assets/fonts/myfont2.ttf"), content);
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(),
+ "-o", out_apk,
+ "--no-compress-fonts",
+ "-A", GetTestPath("assets")
+ };
+
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+ io::IFileCollection* zip = apk->GetFileCollection();
+ ASSERT_THAT(zip, Ne(nullptr));
+
+ auto file = zip->FindFile("res/raw/test.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_TRUE(file->WasCompressed());
+
+ file = zip->FindFile("assets/fonts/myfont1.ttf");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/fonts/myfont2.ttf");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+}
+
TEST_F(LinkTest, OverlayStyles) {
StdErrDiagnostics diag;
const std::string compiled_files_dir = GetTestPath("compiled");
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 5c3dfdcadfec..6bdbaaed9858 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -3,6 +3,8 @@
## Version 2.20
- Too many features, bug fixes, and improvements to list since the last minor version update in
2017. This README will be updated more frequently in the future.
+- Added a new flag `--no-compress-fonts`. This can significantly speed up loading fonts from APK
+ assets, at the cost of increasing the storage size of the APK.
## Version 2.19
- Added navigation resource type.