summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp1
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig12
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java96
-rw-r--r--core/api/module-lib-current.txt1
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/app/ActivityManager.java3
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java37
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManagerHelper.java4
-rw-r--r--core/java/android/app/notification.aconfig1
-rw-r--r--core/java/android/content/Intent.java10
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java36
-rw-r--r--core/java/android/os/IpcDataCache.java5
-rw-r--r--core/java/android/os/flags.aconfig8
-rw-r--r--core/java/android/provider/Settings.java28
-rw-r--r--core/java/android/view/ViewRootImpl.java20
-rw-r--r--core/java/android/view/WindowManager.java38
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java42
-rw-r--r--core/java/android/view/contentcapture/flags/content_capture_flags.aconfig1
-rw-r--r--core/java/android/view/inputmethod/RemoteInputConnectionImpl.java188
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig10
-rw-r--r--core/java/android/window/BackMotionEvent.java22
-rw-r--r--core/java/android/window/BackTouchTracker.java9
-rw-r--r--core/java/android/window/DesktopExperienceFlags.java2
-rw-r--r--core/java/android/window/IWindowOrganizerController.aidl11
-rw-r--r--core/java/android/window/ImeOnBackInvokedDispatcher.java6
-rw-r--r--core/java/android/window/WindowContainerTransaction.java62
-rw-r--r--core/java/android/window/WindowOrganizer.java21
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig8
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig10
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java11
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserContentManager.java56
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserDialog.java37
-rw-r--r--core/proto/android/providers/settings/system.proto11
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_call.xml5
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_conversation.xml6
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml5
-rw-r--r--core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java4
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java12
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java403
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java6
-rw-r--r--core/tests/coretests/src/android/view/PendingInsetsControllerTest.java4
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java4
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java44
-rw-r--r--core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java3
-rw-r--r--core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java11
-rw-r--r--core/tests/coretests/src/android/window/BackTouchTrackerTest.kt2
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java3
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java28
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java39
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java4
-rw-r--r--core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java4
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig2
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml16
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java116
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt196
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java139
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleAnimator.kt101
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt67
-rw-r--r--libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt168
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java163
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt167
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt139
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt101
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt106
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java69
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java132
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt8
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java4
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java7
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt28
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt24
-rw-r--r--packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java20
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt7
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt14
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt1
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt27
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreferenceBinding.kt (renamed from packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt)4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java29
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt90
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java19
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt124
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/OWNERS4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java6
-rw-r--r--packages/SystemUI/Android.bp3
-rw-r--r--packages/SystemUI/TEST_OWNERS3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt193
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java27
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java74
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt)9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt21
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java10
-rw-r--r--ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java2
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt4
-rw-r--r--ravenwood/tests/resapk_test/Android.bp3
-rw-r--r--ravenwood/tests/resapk_test/apk/Android.bp8
-rw-r--r--ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml8
-rw-r--r--ravenwood/tests/resapk_test/apk/res/values/strings.xml5
-rw-r--r--ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java91
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java42
-rw-r--r--services/core/java/com/android/server/am/OWNERS2
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java4
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java4
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java85
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java2
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java10
-rw-r--r--services/core/java/com/android/server/storage/StorageUserConnection.java40
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java23
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java17
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java10
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java167
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java4
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java7
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java26
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java7
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java37
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java87
-rw-r--r--services/core/java/com/android/server/wm/LaunchParamsController.java36
-rw-r--r--services/core/java/com/android/server/wm/PinnedTaskController.java102
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java17
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java21
-rw-r--r--services/core/java/com/android/server/wm/Transition.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java43
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java43
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java115
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java3
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java8
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java378
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java (renamed from services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java)0
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java8
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java9
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java2
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java50
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java153
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java86
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java40
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java4
-rw-r--r--services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java11
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java40
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java8
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java9
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java62
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java102
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DimmerTests.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java154
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java207
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java87
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java152
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java46
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java4
-rw-r--r--telephony/java/android/telephony/TelephonyFrameworkInitializer.java4
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java4
-rw-r--r--tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java523
-rw-r--r--tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt4
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java10
344 files changed, 7629 insertions, 2890 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 4d9fdf041d58..1b0ee6397784 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -265,6 +265,7 @@ java_aconfig_library {
cc_aconfig_library {
name: "com.android.window.flags.window-aconfig_flags_c_lib",
aconfig_declarations: "com.android.window.flags.window-aconfig",
+ host_supported: true,
}
// DeviceStateManager
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 876274ecc32e..aae5bb31273b 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -126,3 +126,15 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "tune_quota_window_default_parameters"
+ namespace: "backstage_power"
+ description: "Tune default active/exempted bucket quota parameters"
+ bug: "401767691"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 54d337eded7d..a9c4a1501dd8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -360,13 +360,13 @@ public final class QuotaController extends StateController {
/** How much time each app will have to run jobs within their standby bucket window. */
private final long[] mAllowedTimePerPeriodMs = new long[]{
- QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS,
0, // NEVER
QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
- QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS
+ QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS
};
/**
@@ -3178,9 +3178,11 @@ public final class QuotaController extends StateController {
static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS =
QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms";
- private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+ // Legacy default time each app will have to run jobs within EXEMPTED bucket
+ private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
10 * 60 * 1000L; // 10 minutes
- private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ // Legacy default time each app will have to run jobs within ACTIVE bucket
+ private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS =
10 * 60 * 1000L; // 10 minutes
@@ -3192,14 +3194,26 @@ public final class QuotaController extends StateController {
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
10 * 60 * 1000L; // 10 minutes
+
+ // Current default time each app will have to run jobs within EXEMPTED bucket
+ private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+ 20 * 60 * 1000L; // 20 minutes
+ // Current default time each app will have to run jobs within ACTIVE bucket
+ private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ 20 * 60 * 1000L; // 20 minutes
+ private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
+ 20 * 60 * 1000L; // 20 minutes
+
private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
30 * 1000L; // 30 seconds
// Legacy default window size for EXEMPTED bucket
+ // EXEMPT apps can run jobs at any time
private static final long DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS =
- DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
// Legacy default window size for ACTIVE bucket
+ // ACTIVE apps can run jobs at any time
private static final long DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS =
- DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
// Legacy default window size for WORKING bucket
private static final long DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS =
2 * 60 * 60 * 1000L; // 2 hours
@@ -3216,6 +3230,13 @@ public final class QuotaController extends StateController {
private static final long DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS =
12 * 60 * 60 * 1000L; // 12 hours
+ // Latest default window size for EXEMPTED bucket.
+ private static final long DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS =
+ 40 * 60 * 1000L; // 40 minutes.
+ // Latest default window size for ACTIVE bucket.
+ private static final long DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS =
+ 60 * 60 * 1000L; // 60 minutes.
+
private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS =
@@ -3276,12 +3297,13 @@ public final class QuotaController extends StateController {
* bucket window.
*/
public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
- DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
/**
* How much time each app in the active bucket will have to run jobs within their standby
* bucket window.
*/
- public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
/**
* How much time each app in the working set bucket will have to run jobs within their
* standby bucket window.
@@ -3575,11 +3597,30 @@ public final class QuotaController extends StateController {
public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
void adjustDefaultBucketWindowSizes() {
- WINDOW_SIZE_EXEMPTED_MS = DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS;
- WINDOW_SIZE_ACTIVE_MS = DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS;
+ ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS :
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS :
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS :
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS;
+
+ WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS :
+ DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS;
+ WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS :
+ DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS;
WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS;
WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS;
+ mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = Math.min(MAX_PERIOD_MS,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS));
+ mAllowedTimePerPeriodMs[ACTIVE_INDEX] = Math.min(MAX_PERIOD_MS,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS));
+
mBucketPeriodsMs[EXEMPTED_INDEX] = Math.max(
mAllowedTimePerPeriodMs[EXEMPTED_INDEX],
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS));
@@ -3592,6 +3633,11 @@ public final class QuotaController extends StateController {
mBucketPeriodsMs[FREQUENT_INDEX] = Math.max(
mAllowedTimePerPeriodMs[FREQUENT_INDEX],
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
+
+ mAllowedTimePeriodAdditionaInstallerMs =
+ Math.min(mBucketPeriodsMs[EXEMPTED_INDEX]
+ - mAllowedTimePerPeriodMs[EXEMPTED_INDEX],
+ ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS);
}
void adjustDefaultEjLimits() {
@@ -3882,10 +3928,14 @@ public final class QuotaController extends StateController {
KEY_WINDOW_SIZE_RESTRICTED_MS);
ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
- DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS);
+ Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS :
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS);
ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
- DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS);
+ Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS :
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS);
ALLOWED_TIME_PER_PERIOD_WORKING_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS);
@@ -3900,19 +3950,27 @@ public final class QuotaController extends StateController {
DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS);
ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS,
- DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS);
+ Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS
+ : DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS);
IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS,
DEFAULT_IN_QUOTA_BUFFER_MS);
MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
DEFAULT_MAX_EXECUTION_TIME_MS);
WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS,
- Flags.adjustQuotaDefaultConstants()
- ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS :
- DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS);
+ (Flags.adjustQuotaDefaultConstants()
+ && Flags.tuneQuotaWindowDefaultParameters())
+ ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS :
+ (Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS));
WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS,
- Flags.adjustQuotaDefaultConstants()
- ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS :
- DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS);
+ (Flags.adjustQuotaDefaultConstants()
+ && Flags.tuneQuotaWindowDefaultParameters())
+ ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS :
+ (Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS));
WINDOW_SIZE_WORKING_MS =
properties.getLong(KEY_WINDOW_SIZE_WORKING_MS,
Flags.adjustQuotaDefaultConstants()
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 132c65cc26ee..526a213a6003 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -410,7 +410,6 @@ package android.os {
method public void invalidateCache();
method public static void invalidateCache(@NonNull String, @NonNull String);
method @Nullable public Result query(@NonNull Query);
- method @FlaggedApi("android.os.ipc_data_cache_test_apis") public static void setTestMode(boolean);
field public static final String MODULE_BLUETOOTH = "bluetooth";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 12bfccf2172c..4c8283907712 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2460,7 +2460,7 @@ package android.os {
method public static void invalidateCache(@NonNull String, @NonNull String);
method public final boolean isDisabled();
method @Nullable public Result query(@NonNull Query);
- method @FlaggedApi("android.os.ipc_data_cache_test_apis") public static void setTestMode(boolean);
+ method public static void setTestMode(boolean);
field public static final String MODULE_BLUETOOTH = "bluetooth";
field public static final String MODULE_SYSTEM = "system_server";
field public static final String MODULE_TEST = "test";
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 54ab3b8f185b..62816a2fa0f5 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3099,7 +3099,8 @@ public class ActivityManager {
/**
* Flag for {@link #moveTaskToFront(int, int)}: also move the "home"
* activity along with the task, so it is positioned immediately behind
- * the task.
+ * the task. This flag is ignored if the task's windowing mode is
+ * {@link WindowConfiguration#WINDOWING_MODE_MULTI_WINDOW}.
*/
public static final int MOVE_TASK_WITH_HOME = 0x00000001;
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 6e495768bfd4..38141cf20ce3 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1417,36 +1417,7 @@ public class PropertyInvalidatedCache<Query, Result> {
}
/**
- * Throw if the current process is not allowed to use test APIs.
- */
- @android.ravenwood.annotation.RavenwoodReplace
- private static void throwIfNotTest() {
- final ActivityThread activityThread = ActivityThread.currentActivityThread();
- if (activityThread == null) {
- // Only tests can reach here.
- return;
- }
- final Instrumentation instrumentation = activityThread.getInstrumentation();
- if (instrumentation == null) {
- // Only tests can reach here.
- return;
- }
- if (instrumentation.isInstrumenting()) {
- return;
- }
- if (Flags.enforcePicTestmodeProtocol()) {
- throw new IllegalStateException("Test-only API called not from a test.");
- }
- }
-
- /**
- * Do not throw if running under ravenwood.
- */
- private static void throwIfNotTest$ravenwood() {
- }
-
- /**
- * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is
+ * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is
* illegal to clear the test mode if the test mode is already off. Enabling test mode puts
* all caches in the process into test mode; all nonces are initialized to UNSET and
* subsequent reads and writes are to process memory. This has the effect of disabling all
@@ -1454,12 +1425,10 @@ public class PropertyInvalidatedCache<Query, Result> {
* operation.
* @param mode The desired test mode.
* @throws IllegalStateException if the supplied mode is already set.
- * @throws IllegalStateException if the process is not running an instrumentation test.
* @hide
*/
@VisibleForTesting
public static void setTestMode(boolean mode) {
- throwIfNotTest();
synchronized (sGlobalLock) {
if (sTestMode == mode) {
final String msg = "cannot set test mode redundantly: mode=" + mode;
@@ -1495,11 +1464,9 @@ public class PropertyInvalidatedCache<Query, Result> {
* for which it would not otherwise have permission. Caches in test mode do NOT write their
* values to the system properties. The effect is local to the current process. Test mode
* must be true when this method is called.
- * @throws IllegalStateException if the process is not running an instrumentation test.
* @hide
*/
public void testPropertyName() {
- throwIfNotTest();
synchronized (sGlobalLock) {
if (sTestMode == false) {
throw new IllegalStateException("cannot test property name with test mode off");
@@ -1810,12 +1777,10 @@ public class PropertyInvalidatedCache<Query, Result> {
* When multiple caches share a single property value, using an instance method on one of
* the cache objects to invalidate all of the cache objects becomes confusing and you should
* just use the static version of this function.
- * @throws IllegalStateException if the process is not running an instrumentation test.
* @hide
*/
@VisibleForTesting
public void disableSystemWide() {
- throwIfNotTest();
disableSystemWide(mPropertyName);
}
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index 83abc048af8a..e05ede580d3f 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -31,6 +31,7 @@ import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GlobalSearchSession;
import android.app.appsearch.JoinSpec;
+import android.app.appsearch.PropertyPath;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
@@ -141,6 +142,9 @@ public class AppFunctionManagerHelper {
.addFilterSchemas(
AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
targetPackage))
+ .addProjectionPaths(
+ SearchSpec.SCHEMA_TYPE_WILDCARD,
+ List.of(new PropertyPath(STATIC_PROPERTY_ENABLED_BY_DEFAULT)))
.setJoinSpec(joinSpec)
.setVerbatimSearchEnabled(true)
.build();
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 3eaf2c40daca..5891bddfbbe6 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -367,6 +367,7 @@ flag {
namespace: "systemui"
description: "Allows the NAS to summarize notifications"
bug: "390417189"
+ is_exported: true
}
flag {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 038756148a32..bb62ac321202 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12426,6 +12426,8 @@ public class Intent implements Parcelable, Cloneable {
}
private void collectNestedIntentKeysRecur(Set<Intent> visited, boolean forceUnparcel) {
+ // if forceUnparcel is false, do not unparcel the mExtras bundle.
+ // forceUnparcel will only be true when this method is called from system server.
if (mExtras != null && (forceUnparcel || !mExtras.isParcelled()) && !mExtras.isEmpty()) {
addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
for (String key : mExtras.keySet()) {
@@ -12440,6 +12442,7 @@ public class Intent implements Parcelable, Cloneable {
value = mExtras.get(key);
} else {
value = null;
+ removeExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
}
} catch (BadParcelableException e) {
// This may still happen if the keys are collected on the system server side, in
@@ -12459,6 +12462,13 @@ public class Intent implements Parcelable, Cloneable {
}
}
+ // if there is no extras in the bundle, we also mark the intent as keys are collected.
+ // isDefinitelyEmpty() will not unparceled the mExtras. This is the best we can do without
+ // unparceling the extra bundle.
+ if (mExtras == null || mExtras.isDefinitelyEmpty()) {
+ addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
+ }
+
if (mClipData != null) {
for (int i = 0; i < mClipData.getItemCount(); i++) {
Intent intent = mClipData.getItemAt(i).mIntent;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 10d3051cff6f..ded35b23608d 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -31,13 +31,13 @@ import android.os.Environment;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
-import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseArrayMap;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -98,15 +98,16 @@ public abstract class RegisteredServicesCache<V> {
@GuardedBy("mServicesLock")
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
- @GuardedBy("mServiceInfoCaches")
- private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+ @GuardedBy("mUserIdToServiceInfoCaches")
+ private final SparseArrayMap<ComponentName, ServiceInfo<V>> mUserIdToServiceInfoCaches =
+ new SparseArrayMap<>();
private final Handler mBackgroundHandler;
private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
public void run() {
- synchronized (mServiceInfoCaches) {
- mServiceInfoCaches.clear();
+ synchronized (mUserIdToServiceInfoCaches) {
+ mUserIdToServiceInfoCaches.clear();
}
}
};
@@ -537,8 +538,8 @@ public abstract class RegisteredServicesCache<V> {
Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e);
}
if (lastUpdateTime >= 0) {
- ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName,
- lastUpdateTime);
+ ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(userId,
+ componentName, lastUpdateTime);
if (serviceInfo != null) {
serviceInfos.add(serviceInfo);
continue;
@@ -553,8 +554,8 @@ public abstract class RegisteredServicesCache<V> {
}
serviceInfos.add(info);
if (Flags.optimizeParsingInRegisteredServicesCache()) {
- synchronized (mServiceInfoCaches) {
- mServiceInfoCaches.put(componentName, info);
+ synchronized (mUserIdToServiceInfoCaches) {
+ mUserIdToServiceInfoCaches.add(userId, componentName, info);
}
}
} catch (XmlPullParserException | IOException e) {
@@ -563,8 +564,8 @@ public abstract class RegisteredServicesCache<V> {
}
if (Flags.optimizeParsingInRegisteredServicesCache()) {
- synchronized (mServiceInfoCaches) {
- if (!mServiceInfoCaches.isEmpty()) {
+ synchronized (mUserIdToServiceInfoCaches) {
+ if (mUserIdToServiceInfoCaches.numMaps() > 0) {
mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
@@ -873,6 +874,11 @@ public abstract class RegisteredServicesCache<V> {
synchronized (mServicesLock) {
mUserServices.remove(userId);
}
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mUserIdToServiceInfoCaches) {
+ mUserIdToServiceInfoCaches.delete(userId);
+ }
+ }
}
@VisibleForTesting
@@ -916,10 +922,10 @@ public abstract class RegisteredServicesCache<V> {
mContext.unregisterReceiver(mUserRemovedReceiver);
}
- private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName,
- long lastUpdateTime) {
- synchronized (mServiceInfoCaches) {
- ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName);
+ private ServiceInfo<V> getServiceInfoFromServiceCache(int userId,
+ @NonNull ComponentName componentName, long lastUpdateTime) {
+ synchronized (mUserIdToServiceInfoCaches) {
+ ServiceInfo<V> serviceCache = mUserIdToServiceInfoCaches.get(userId, componentName);
if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) {
return serviceCache;
}
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index e888f520b842..2e7c3be53d90 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -718,7 +718,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
}
/**
- * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is
+ * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is
* illegal to clear the test mode if the test mode is already off. Enabling test mode puts
* all caches in the process into test mode; all nonces are initialized to UNSET and
* subsequent reads and writes are to process memory. This has the effect of disabling all
@@ -726,11 +726,8 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
* operation.
* @param mode The desired test mode.
* @throws IllegalStateException if the supplied mode is already set.
- * @throws IllegalStateException if the process is not running an instrumentation test.
* @hide
*/
- @FlaggedApi(android.os.Flags.FLAG_IPC_DATA_CACHE_TEST_APIS)
- @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
@TestApi
public static void setTestMode(boolean mode) {
PropertyInvalidatedCache.setTestMode(mode);
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 5d80119410e1..86acb2b21cfa 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -227,14 +227,6 @@ flag {
}
flag {
- name: "ipc_data_cache_test_apis"
- namespace: "system_performance"
- description: "Expose IpcDataCache test apis to mainline modules."
- bug: "396173886"
- is_exported: true
-}
-
-flag {
name: "mainline_vcn_platform_api"
namespace: "vcn"
description: "Expose platform APIs to mainline VCN"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e12412b40110..d342494e32e6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4184,7 +4184,6 @@ public final class Settings {
MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_REDIRECT_URL);
MOVED_TO_SECURE.add(Secure.SETTINGS_CLASSNAME);
MOVED_TO_SECURE.add(Secure.USE_GOOGLE_MAIL);
- MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY);
MOVED_TO_SECURE.add(Secure.WIFI_NUM_OPEN_NETWORKS_KEPT);
MOVED_TO_SECURE.add(Secure.WIFI_ON);
@@ -4223,6 +4222,7 @@ public final class Settings {
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED);
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS);
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_MAX_DHCP_RETRY_COUNT);
+ MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
// these are moving directly from system to global
MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_ON);
@@ -6484,6 +6484,14 @@ public final class Settings {
public static final String SCREEN_FLASH_NOTIFICATION = "screen_flash_notification";
/**
+ * Setting to enable CV (proprietary)
+ *
+ * @hide
+ */
+ public static final String CV_ENABLED =
+ "cv_enabled";
+
+ /**
* Integer property that specifes the color for screen flash notification as a
* packed 32-bit color.
*
@@ -20815,6 +20823,24 @@ public final class Settings {
@Readable
public static final String WEAR_LAUNCHER_UI_MODE = "wear_launcher_ui_mode";
+ /**
+ * Setting indicating whether the primary gesture input action has been enabled by the
+ * user.
+ *
+ * @hide
+ */
+ public static final String GESTURE_PRIMARY_ACTION_USER_PREFERENCE =
+ "gesture_primary_action_user_preference";
+
+ /**
+ * Setting indicating whether the dismiss gesture input action has been enabled by the
+ * user.
+ *
+ * @hide
+ */
+ public static final String GESTURE_DISMISS_ACTION_USER_PREFERENCE =
+ "gesture_dismiss_action_user_preference";
+
/** Whether Wear Power Anomaly Service is enabled.
*
* (0 = false, 1 = true)
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index da91e8d2d256..2edce5de7ace 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -555,8 +555,6 @@ public final class ViewRootImpl implements ViewParent,
@UiContext
public final Context mContext;
- private UiModeManager mUiModeManager;
-
@UnsupportedAppUsage
final IWindowSession mWindowSession;
@NonNull Display mDisplay;
@@ -2079,8 +2077,7 @@ public final class ViewRootImpl implements ViewParent,
// We also ignore dark theme, since the app developer can override the user's
// preference for dark mode in configuration.uiMode. Instead, we assume that both
// force invert and the system's dark theme are enabled.
- if (getUiModeManager().getForceInvertState() ==
- UiModeManager.FORCE_INVERT_TYPE_DARK) {
+ if (shouldApplyForceInvertDark()) {
final boolean isLightTheme =
a.getBoolean(R.styleable.Theme_isLightTheme, false);
// TODO: b/372558459 - Also check the background ColorDrawable color lightness
@@ -2108,6 +2105,14 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ private boolean shouldApplyForceInvertDark() {
+ final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
+ if (uiModeManager == null) {
+ return false;
+ }
+ return uiModeManager.getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK;
+ }
+
private void updateForceDarkMode() {
if (mAttachInfo.mThreadedRenderer == null) return;
if (mAttachInfo.mThreadedRenderer.setForceDark(determineForceDarkType())) {
@@ -9413,13 +9418,6 @@ public final class ViewRootImpl implements ViewParent,
return mAudioManager;
}
- private UiModeManager getUiModeManager() {
- if (mUiModeManager == null) {
- mUiModeManager = mContext.getSystemService(UiModeManager.class);
- }
- return mUiModeManager;
- }
-
private Vibrator getSystemVibrator() {
if (mVibrator == null) {
mVibrator = mContext.getSystemService(Vibrator.class);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 83dc79beb75e..315f1ba58529 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1514,6 +1514,44 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY";
/**
+ * Application or Activity level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property} that specifies
+ * whether this package or activity wants to allow safe region letterboxing. A safe
+ * region policy may be applied by the system to improve the user experience by ensuring that
+ * the activity does not have any content that is occluded and has the correct current
+ * window metrics.
+ *
+ * <p>Not setting the property at all defaults it to {@code true}. In such a case, the activity
+ * will be letterboxed in the safe region.
+ *
+ * <p>To not allow the safe region letterboxing, add this property to your app
+ * manifest and set the value to {@code false}. An app should ignore safe region
+ * letterboxing if it can handle bounds and insets from all four directions correctly when a
+ * request to go immersive is denied by the system. If the application does not allow safe
+ * region letterboxing, the system will not override this behavior.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;application&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING"
+ * android:value="false"/&gt;
+ * &lt;/application&gt;
+ * </pre>or
+ * <pre>
+ * &lt;activity&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING"
+ * android:value="false"/&gt;
+ * &lt;/activity&gt;
+ * </pre>
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ String PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING =
+ "android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e43fb48527a1..c97c4acf706c 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -866,31 +866,18 @@ public final class AccessibilityManager {
}
/**
- * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
- * for a given feedback type.
- *
- * @param feedbackTypeFlags The feedback type flags.
- * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
- *
- * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
- * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
- * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
- * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
- * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
- * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
+ * @see #getEnabledAccessibilityServiceList(int)
+ * @hide
*/
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
- int feedbackTypeFlags) {
+ int feedbackTypeFlags, int userId) {
final IAccessibilityManager service;
- final int userId;
synchronized (mLock) {
service = getServiceLocked();
if (service == null) {
return Collections.emptyList();
}
- userId = mUserId;
}
-
List<AccessibilityServiceInfo> services = null;
try {
services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
@@ -912,6 +899,29 @@ public final class AccessibilityManager {
}
/**
+ * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
+ * for a given feedback type.
+ *
+ * @param feedbackTypeFlags The feedback type flags.
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ *
+ * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
+ * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
+ * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
+ * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
+ * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+ * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
+ */
+ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
+ int feedbackTypeFlags) {
+ final int userId;
+ synchronized (mLock) {
+ userId = mUserId;
+ }
+ return getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
+ }
+
+ /**
* Returns whether the user must be shown the AccessibilityService warning dialog
* before the AccessibilityService (or any shortcut for the service) can be enabled.
* @hide
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index 8c98fa455cc8..3780db38ec84 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -21,6 +21,7 @@ flag {
namespace: "pixel_state_server"
description: "Feature flag to send a flush event after each frame"
bug: "380381249"
+ is_exported: true
is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index 3557f16a6dc8..ced27d6d4886 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,7 +28,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UiThread;
import android.app.UriGrantsManager;
import android.content.ContentProvider;
import android.content.Intent;
@@ -38,6 +37,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.CancellationSignalBeamer;
+import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -468,13 +468,27 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
});
}
+ /**
+ * Returns {@code false} if there is a sessionId mismatch and logs the event.
+ */
+ private boolean checkSessionId(@NonNull InputConnectionCommandHeader header) {
+ if (header.mSessionId != mCurrentSessionId.get()) {
+ Log.w(TAG, "Session id mismatch header.sessionId: " + header.mSessionId
+ + " currentSessionId: " + mCurrentSessionId.get() + " while calling "
+ + Debug.getCaller());
+ //TODO(b/396066692): log metrics.
+ return false; // cancelled
+ }
+ return true;
+ }
+
@Dispatching(cancellable = true)
@Override
public void getTextAfterCursor(InputConnectionCommandHeader header, int length, int flags,
AndroidFuture future /* T=CharSequence */) {
dispatchWithTracing("getTextAfterCursor", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -495,8 +509,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getTextBeforeCursor(InputConnectionCommandHeader header, int length, int flags,
AndroidFuture future /* T=CharSequence */) {
dispatchWithTracing("getTextBeforeCursor", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -517,8 +531,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getSelectedText(InputConnectionCommandHeader header, int flags,
AndroidFuture future /* T=CharSequence */) {
dispatchWithTracing("getSelectedText", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -539,8 +553,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength,
int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) {
dispatchWithTracing("getSurroundingText", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -567,8 +581,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes,
AndroidFuture future /* T=Integer */) {
dispatchWithTracing("getCursorCapsMode", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return 0; // cancelled
+ if (!checkSessionId(header)) {
+ return 0;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -584,8 +598,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getExtractedText(InputConnectionCommandHeader header, ExtractedTextRequest request,
int flags, AndroidFuture future /* T=ExtractedText */) {
dispatchWithTracing("getExtractedText", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -601,8 +615,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void commitText(InputConnectionCommandHeader header, CharSequence text,
int newCursorPosition) {
dispatchWithTracing("commitText", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return;
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -618,8 +632,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void commitTextWithTextAttribute(InputConnectionCommandHeader header, CharSequence text,
int newCursorPosition, @Nullable TextAttribute textAttribute) {
dispatchWithTracing("commitTextWithTextAttribute", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -634,8 +648,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void commitCompletion(InputConnectionCommandHeader header, CompletionInfo text) {
dispatchWithTracing("commitCompletion", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -650,8 +664,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void commitCorrection(InputConnectionCommandHeader header, CorrectionInfo info) {
dispatchWithTracing("commitCorrection", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -670,8 +684,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void setSelection(InputConnectionCommandHeader header, int start, int end) {
dispatchWithTracing("setSelection", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -686,8 +700,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performEditorAction(InputConnectionCommandHeader header, int id) {
dispatchWithTracing("performEditorAction", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -702,8 +716,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performContextMenuAction(InputConnectionCommandHeader header, int id) {
dispatchWithTracing("performContextMenuAction", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -718,8 +732,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void setComposingRegion(InputConnectionCommandHeader header, int start, int end) {
dispatchWithTracing("setComposingRegion", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -739,8 +753,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void setComposingRegionWithTextAttribute(InputConnectionCommandHeader header, int start,
int end, @Nullable TextAttribute textAttribute) {
dispatchWithTracing("setComposingRegionWithTextAttribute", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -756,8 +770,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void setComposingText(InputConnectionCommandHeader header, CharSequence text,
int newCursorPosition) {
dispatchWithTracing("setComposingText", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -773,8 +787,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void setComposingTextWithTextAttribute(InputConnectionCommandHeader header,
CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute) {
dispatchWithTracing("setComposingTextWithTextAttribute", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -826,8 +840,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
}
return;
}
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null && mDeactivateRequested.get()) {
@@ -842,8 +856,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) {
dispatchWithTracing("sendKeyEvent", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -858,8 +872,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) {
dispatchWithTracing("clearMetaKeyStates", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -875,8 +889,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength,
int afterLength) {
dispatchWithTracing("deleteSurroundingText", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -892,8 +906,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void deleteSurroundingTextInCodePoints(InputConnectionCommandHeader header,
int beforeLength, int afterLength) {
dispatchWithTracing("deleteSurroundingTextInCodePoints", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -912,8 +926,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void beginBatchEdit(InputConnectionCommandHeader header) {
dispatchWithTracing("beginBatchEdit", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -928,8 +942,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void endBatchEdit(InputConnectionCommandHeader header) {
dispatchWithTracing("endBatchEdit", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -944,8 +958,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performSpellCheck(InputConnectionCommandHeader header) {
dispatchWithTracing("performSpellCheck", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -961,8 +975,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void performPrivateCommand(InputConnectionCommandHeader header, String action,
Bundle data) {
dispatchWithTracing("performPrivateCommand", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -995,12 +1009,12 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
}
}
dispatchWithTracing("performHandwritingGesture", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
+ if (!checkSessionId(header)) {
if (resultReceiver != null) {
resultReceiver.send(
InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED, null);
}
- return; // cancelled
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1038,9 +1052,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
(PreviewableHandwritingGesture) gestureContainer.get();
dispatchWithTracing("previewHandwritingGesture", () -> {
- if (header.mSessionId != mCurrentSessionId.get()
+ if (!checkSessionId(header)
|| (cancellationSignal != null && cancellationSignal.isCanceled())) {
- return; // cancelled
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1065,8 +1079,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
int imeDisplayId, AndroidFuture future /* T=Boolean */) {
dispatchWithTracing("requestCursorUpdates", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return false; // cancelled
+ if (!checkSessionId(header)) {
+ return false; // cancelled.
}
return requestCursorUpdatesInternal(
cursorUpdateMode, 0 /* cursorUpdateFilter */, imeDisplayId);
@@ -1079,8 +1093,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId,
AndroidFuture future /* T=Boolean */) {
dispatchWithTracing("requestCursorUpdates", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return false; // cancelled
+ if (!checkSessionId(header)) {
+ return false; // cancelled.
}
return requestCursorUpdatesInternal(
cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
@@ -1123,9 +1137,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
InputConnectionCommandHeader header, RectF bounds,
@NonNull ResultReceiver resultReceiver) {
dispatchWithTracing("requestTextBoundsInfo", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
+ if (!checkSessionId(header)) {
resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
- return; // cancelled
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1168,8 +1182,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
return false;
}
- if (header.mSessionId != mCurrentSessionId.get()) {
- return false; // cancelled
+ if (!checkSessionId(header)) {
+ return false; // cancelled.
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1193,8 +1207,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void setImeConsumesInput(InputConnectionCommandHeader header, boolean imeConsumesInput) {
dispatchWithTracing("setImeConsumesInput", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1217,8 +1231,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
dispatchWithTracing(
"replaceText",
() -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1236,8 +1250,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void commitText(InputConnectionCommandHeader header, CharSequence text,
int newCursorPosition, @Nullable TextAttribute textAttribute) {
dispatchWithTracing("commitTextFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1256,8 +1270,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void setSelection(InputConnectionCommandHeader header, int start, int end) {
dispatchWithTracing("setSelectionFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1273,8 +1287,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength,
int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) {
dispatchWithTracing("getSurroundingTextFromA11yIme", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null; // cancelled.
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1301,8 +1315,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength,
int afterLength) {
dispatchWithTracing("deleteSurroundingTextFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1317,8 +1331,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) {
dispatchWithTracing("sendKeyEventFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1333,8 +1347,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performEditorAction(InputConnectionCommandHeader header, int id) {
dispatchWithTracing("performEditorActionFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1349,8 +1363,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performContextMenuAction(InputConnectionCommandHeader header, int id) {
dispatchWithTracing("performContextMenuActionFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1366,8 +1380,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes,
AndroidFuture future /* T=Integer */) {
dispatchWithTracing("getCursorCapsModeFromA11yIme", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return 0; // cancelled
+ if (!checkSessionId(header)) {
+ return 0; // cancelled.
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1382,8 +1396,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) {
dispatchWithTracing("clearMetaKeyStatesFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index a4ea64e5811e..67e54423414c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -214,3 +214,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "invalidate_input_calls_restart"
+ namespace: "input_method"
+ description: "Feature flag to fix the race between invalidateInput and restartInput"
+ bug: "396066692"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index cc2afbc6aaa3..d53c787749d9 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -18,7 +18,6 @@ package android.window;
import android.annotation.FloatRange;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.RemoteAnimationTarget;
@@ -39,8 +38,6 @@ public final class BackMotionEvent implements Parcelable {
@BackEvent.SwipeEdge
private final int mSwipeEdge;
- @Nullable
- private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
* Creates a new {@link BackMotionEvent} instance.
@@ -53,8 +50,6 @@ public final class BackMotionEvent implements Parcelable {
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param triggerBack Indicates whether the back arrow is in the triggered state or not
* @param swipeEdge Indicates which edge the swipe starts from.
- * @param departingAnimationTarget The remote animation target of the departing
- * application window.
*/
public BackMotionEvent(
float touchX,
@@ -62,15 +57,13 @@ public final class BackMotionEvent implements Parcelable {
long frameTimeMillis,
float progress,
boolean triggerBack,
- @BackEvent.SwipeEdge int swipeEdge,
- @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ @BackEvent.SwipeEdge int swipeEdge) {
mTouchX = touchX;
mTouchY = touchY;
mFrameTimeMillis = frameTimeMillis;
mProgress = progress;
mTriggerBack = triggerBack;
mSwipeEdge = swipeEdge;
- mDepartingAnimationTarget = departingAnimationTarget;
}
private BackMotionEvent(@NonNull Parcel in) {
@@ -79,7 +72,6 @@ public final class BackMotionEvent implements Parcelable {
mProgress = in.readFloat();
mTriggerBack = in.readBoolean();
mSwipeEdge = in.readInt();
- mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
mFrameTimeMillis = in.readLong();
}
@@ -108,7 +100,6 @@ public final class BackMotionEvent implements Parcelable {
dest.writeFloat(mProgress);
dest.writeBoolean(mTriggerBack);
dest.writeInt(mSwipeEdge);
- dest.writeTypedObject(mDepartingAnimationTarget, flags);
dest.writeLong(mFrameTimeMillis);
}
@@ -160,16 +151,6 @@ public final class BackMotionEvent implements Parcelable {
return mFrameTimeMillis;
}
- /**
- * Returns the {@link RemoteAnimationTarget} of the top departing application window,
- * or {@code null} if the top window should not be moved for the current type of back
- * destination.
- */
- @Nullable
- public RemoteAnimationTarget getDepartingAnimationTarget() {
- return mDepartingAnimationTarget;
- }
-
@Override
public String toString() {
return "BackMotionEvent{"
@@ -179,7 +160,6 @@ public final class BackMotionEvent implements Parcelable {
+ ", mProgress=" + mProgress
+ ", mTriggerBack=" + mTriggerBack
+ ", mSwipeEdge=" + mSwipeEdge
- + ", mDepartingAnimationTarget=" + mDepartingAnimationTarget
+ "}";
}
}
diff --git a/core/java/android/window/BackTouchTracker.java b/core/java/android/window/BackTouchTracker.java
index 4908068d51e4..ea1b64066cfe 100644
--- a/core/java/android/window/BackTouchTracker.java
+++ b/core/java/android/window/BackTouchTracker.java
@@ -20,7 +20,6 @@ import android.annotation.FloatRange;
import android.os.SystemProperties;
import android.util.MathUtils;
import android.view.MotionEvent;
-import android.view.RemoteAnimationTarget;
import java.io.PrintWriter;
@@ -147,15 +146,14 @@ public class BackTouchTracker {
}
/** Creates a start {@link BackMotionEvent}. */
- public BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
+ public BackMotionEvent createStartEvent() {
return new BackMotionEvent(
/* touchX = */ mInitTouchX,
/* touchY = */ mInitTouchY,
/* frameTimeMillis = */ 0,
/* progress = */ 0,
/* triggerBack = */ mTriggerBack,
- /* swipeEdge = */ mSwipeEdge,
- /* departingAnimationTarget = */ target);
+ /* swipeEdge = */ mSwipeEdge);
}
/** Creates a progress {@link BackMotionEvent}. */
@@ -239,8 +237,7 @@ public class BackTouchTracker {
/* frameTimeMillis = */ 0,
/* progress = */ progress,
/* triggerBack = */ mTriggerBack,
- /* swipeEdge = */ mSwipeEdge,
- /* departingAnimationTarget = */ null);
+ /* swipeEdge = */ mSwipeEdge);
}
/** Sets the thresholds for computing progress. */
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
index 866c16cb566d..e3041c00e9fc 100644
--- a/core/java/android/window/DesktopExperienceFlags.java
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -56,7 +56,9 @@ public enum DesktopExperienceFlags {
ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(
com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement,
true),
+ ENABLE_DISPLAY_DISCONNECT_INTERACTION(Flags::enableDisplayDisconnectInteraction, false),
ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, true),
+ ENABLE_DISPLAY_RECONNECT_INTERACTION(Flags::enableDisplayReconnectInteraction, false),
ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, true),
ENABLE_DRAG_TO_MAXIMIZE(Flags::enableDragToMaximize, true),
ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, true),
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 6f4dd4e3c5ed..0b84070c8d26 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -66,17 +66,6 @@ interface IWindowOrganizerController {
void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t);
/**
- * Starts a legacy transition.
- * @param type The transition type.
- * @param adapter The animation to use.
- * @param syncCallback A sync callback for the contents of `t`
- * @param t Operations that are part of the transition.
- * @return sync-id or -1 if this no-op'd because a transition is already running.
- */
- int startLegacyTransition(int type, in RemoteAnimationAdapter adapter,
- in IWindowContainerTransactionCallback syncCallback, in WindowContainerTransaction t);
-
- /**
* Finishes a transition. This must be called for all created transitions.
* @param transitionToken Which transition to finish
* @param t Changes to make before finishing but in the same SF Transaction. Can be null.
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index d478108d928a..69613a748884 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -270,8 +270,7 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
}
mIOnBackInvokedCallback.onBackStarted(
new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
- backEvent.getProgress(), false, backEvent.getSwipeEdge(),
- null));
+ backEvent.getProgress(), false, backEvent.getSwipeEdge()));
} catch (RemoteException e) {
Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
}
@@ -286,8 +285,7 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
}
mIOnBackInvokedCallback.onBackProgressed(
new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
- backEvent.getProgress(), false, backEvent.getSwipeEdge(),
- null));
+ backEvent.getProgress(), false, backEvent.getSwipeEdge()));
} catch (RemoteException e) {
Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 485e7b33f3a7..2ed9c3a48add 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -445,6 +445,27 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ /**
+ * Sets a given safe region {@code Rect} on the {@code container}. Set {@code null} to reset
+ * safe region bounds. When a safe region is set on a WindowContainer, the activities which
+ * need to be within a safe region will be letterboxed within the set safe region bounds.
+ * <p>Note that if the position of the WindowContainer changes, the caller needs to update the
+ * safe region bounds.
+ *
+ * @param container The window container that the safe region bounds are set on
+ * @param safeRegionBounds The rect for the safe region bounds which are absolute in nature.
+ * @hide
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public WindowContainerTransaction setSafeRegionBounds(
+ @NonNull WindowContainerToken container,
+ @Nullable Rect safeRegionBounds) {
+ mHierarchyOps.add(
+ HierarchyOp.createForSetSafeRegionBounds(container.asBinder(), safeRegionBounds));
+ return this;
+ }
+
/*
* ===========================================================================================
* Hierarchy updates (create/destroy/reorder/reparent containers)
@@ -1597,6 +1618,7 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23;
public static final int HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK = 24;
public static final int HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY = 25;
+ public static final int HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS = 26;
@IntDef(prefix = {"HIERARCHY_OP_TYPE_"}, value = {
HIERARCHY_OP_TYPE_REPARENT,
@@ -1625,6 +1647,7 @@ public final class WindowContainerTransaction implements Parcelable {
HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT,
HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK,
HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY,
+ HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface HierarchyOpType {
@@ -1710,6 +1733,9 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mLaunchAdjacentDisabled;
+ @Nullable
+ private Rect mSafeRegionBounds;
+
/** Creates a hierarchy operation for reparenting a container within the hierarchy. */
@NonNull
public static HierarchyOp createForReparent(
@@ -1873,6 +1899,17 @@ public final class WindowContainerTransaction implements Parcelable {
.build();
}
+ /** Creates a hierarchy op for setting the safe region bounds. */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public static HierarchyOp createForSetSafeRegionBounds(@NonNull IBinder container,
+ @Nullable Rect safeRegionBounds) {
+ return new Builder(HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS)
+ .setContainer(container)
+ .setSafeRegionBounds(safeRegionBounds)
+ .build();
+ }
+
/** Only creates through {@link Builder}. */
private HierarchyOp(@HierarchyOpType int type) {
mType = type;
@@ -1903,6 +1940,7 @@ public final class WindowContainerTransaction implements Parcelable {
mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents;
mExcludeInsetsTypes = copy.mExcludeInsetsTypes;
mLaunchAdjacentDisabled = copy.mLaunchAdjacentDisabled;
+ mSafeRegionBounds = copy.mSafeRegionBounds;
}
private HierarchyOp(@NonNull Parcel in) {
@@ -1930,6 +1968,7 @@ public final class WindowContainerTransaction implements Parcelable {
mIsTrimmableFromRecents = in.readBoolean();
mExcludeInsetsTypes = in.readInt();
mLaunchAdjacentDisabled = in.readBoolean();
+ mSafeRegionBounds = in.readTypedObject(Rect.CREATOR);
}
@HierarchyOpType
@@ -2051,6 +2090,12 @@ public final class WindowContainerTransaction implements Parcelable {
return mLaunchAdjacentDisabled;
}
+ /** Denotes the safe region bounds */
+ @Nullable
+ public Rect getSafeRegionBounds() {
+ return mSafeRegionBounds;
+ }
+
/** Gets a string representation of a hierarchy-op type. */
public static String hopToString(@HierarchyOpType int type) {
switch (type) {
@@ -2084,6 +2129,7 @@ public final class WindowContainerTransaction implements Parcelable {
case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: return "restoreBackNav";
case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE: return "setKeyguardState";
+ case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS: return "setSafeRegionBounds";
default: return "HOP(" + type + ")";
}
}
@@ -2184,6 +2230,11 @@ public final class WindowContainerTransaction implements Parcelable {
sb.append("container= ").append(mContainer)
.append(" isTrimmable= ")
.append(mIsTrimmableFromRecents);
+ break;
+ case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS:
+ sb.append("container= ").append(mContainer)
+ .append(" safeRegionBounds= ")
+ .append(mSafeRegionBounds);
default:
sb.append("container=").append(mContainer)
.append(" reparent=").append(mReparent)
@@ -2220,6 +2271,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeBoolean(mIsTrimmableFromRecents);
dest.writeInt(mExcludeInsetsTypes);
dest.writeBoolean(mLaunchAdjacentDisabled);
+ dest.writeTypedObject(mSafeRegionBounds, flags);
}
@Override
@@ -2305,6 +2357,9 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mLaunchAdjacentDisabled;
+ @Nullable
+ private Rect mSafeRegionBounds;
+
Builder(@HierarchyOpType int type) {
mType = type;
}
@@ -2426,6 +2481,11 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ Builder setSafeRegionBounds(Rect safeRegionBounds) {
+ mSafeRegionBounds = safeRegionBounds;
+ return this;
+ }
+
@NonNull
HierarchyOp build() {
final HierarchyOp hierarchyOp = new HierarchyOp(mType);
@@ -2456,7 +2516,7 @@ public final class WindowContainerTransaction implements Parcelable {
hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents;
hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes;
hierarchyOp.mLaunchAdjacentDisabled = mLaunchAdjacentDisabled;
-
+ hierarchyOp.mSafeRegionBounds = mSafeRegionBounds;
return hierarchyOp;
}
}
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 5c5da49fe543..6e56b6318727 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -130,27 +130,6 @@ public class WindowOrganizer {
}
/**
- * Start a legacy transition.
- * @param type The type of the transition. This is ignored if a transitionToken is provided.
- * @param adapter An existing transition to start. If null, a new transition is created.
- * @param t The set of window operations that are part of this transition.
- * @return true on success, false if a transition was already running.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- @NonNull
- public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
- @NonNull WindowContainerTransactionCallback syncCallback,
- @NonNull WindowContainerTransaction t) {
- try {
- return getWindowOrganizerController().startLegacyTransition(
- type, adapter, syncCallback.mInterface, t);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Register an ITransitionPlayer to handle transition animations.
* @hide
*/
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index a42759e9e23f..cc07616412b6 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -155,4 +155,12 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "backup_and_restore_for_user_aspect_ratio_settings"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether B&R for user aspect ratio settings is enabled"
+ bug: "396650383"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index e706af999117..9781e7046038 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -945,3 +945,13 @@ flag {
description: "Enable restart menu UI, which is shown when an app moves between displays."
bug: "397804287"
}
+
+flag {
+ name: "enable_dynamic_radius_computation_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables bugfix to compute the corner/shadow radius of desktop windows dynamically with the current window context."
+ bug: "399630464"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 1281a78d4fa2..24e80209e9e9 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -136,7 +136,8 @@ public class AccessibilityShortcutController {
private final Context mContext;
private final Handler mHandler;
- private final UserSetupCompleteObserver mUserSetupCompleteObserver;
+ @VisibleForTesting
+ public final UserSetupCompleteObserver mUserSetupCompleteObserver;
private AlertDialog mAlertDialog;
private boolean mIsShortcutEnabled;
@@ -471,7 +472,7 @@ public class AccessibilityShortcutController {
AccessibilityManager accessibilityManager =
mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext);
return accessibilityManager.getEnabledAccessibilityServiceList(
- FEEDBACK_ALL_MASK).contains(serviceInfo);
+ FEEDBACK_ALL_MASK, mUserId).contains(serviceInfo);
}
private boolean hasFeatureLeanback() {
@@ -676,7 +677,8 @@ public class AccessibilityShortcutController {
}
}
- private class UserSetupCompleteObserver extends ContentObserver {
+ @VisibleForTesting
+ public class UserSetupCompleteObserver extends ContentObserver {
private boolean mIsRegistered = false;
private int mUserId;
@@ -749,7 +751,8 @@ public class AccessibilityShortcutController {
R.string.config_defaultAccessibilityService);
final List<AccessibilityServiceInfo> enabledServices =
mFrameworkObjectProvider.getAccessibilityManagerInstance(
- mContext).getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
+ mContext).getEnabledAccessibilityServiceList(
+ FEEDBACK_ALL_MASK, mUserId);
for (int i = enabledServices.size() - 1; i >= 0; i--) {
if (TextUtils.equals(defaultShortcutTarget, enabledServices.get(i).getId())) {
return;
diff --git a/core/java/com/android/internal/app/MediaRouteChooserContentManager.java b/core/java/com/android/internal/app/MediaRouteChooserContentManager.java
new file mode 100644
index 000000000000..09c6f5e6caaa
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteChooserContentManager.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+import com.android.internal.R;
+
+public class MediaRouteChooserContentManager {
+ Context mContext;
+
+ private final boolean mShowProgressBarWhenEmpty;
+
+ public MediaRouteChooserContentManager(Context context, boolean showProgressBarWhenEmpty) {
+ mContext = context;
+ mShowProgressBarWhenEmpty = showProgressBarWhenEmpty;
+ }
+
+ /**
+ * Starts binding all the views (list view, empty view, etc.) using the
+ * given container view.
+ */
+ public void bindViews(View containerView) {
+ View emptyView = containerView.findViewById(android.R.id.empty);
+ ListView listView = containerView.findViewById(R.id.media_route_list);
+ listView.setEmptyView(emptyView);
+
+ if (!mShowProgressBarWhenEmpty) {
+ containerView.findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE);
+
+ // Center the empty view when the progress bar is not shown.
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) emptyView.getLayoutParams();
+ params.gravity = Gravity.CENTER;
+ emptyView.setLayoutParams(params);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
index 23d966fdbc0d..5030a143ea94 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
@@ -23,14 +23,12 @@ import android.media.MediaRouter.RouteInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
-import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
@@ -52,15 +50,15 @@ import java.util.Comparator;
public class MediaRouteChooserDialog extends AlertDialog {
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
- private final boolean mShowProgressBarWhenEmpty;
private int mRouteTypes;
private View.OnClickListener mExtendedSettingsClickListener;
private RouteAdapter mAdapter;
- private ListView mListView;
private Button mExtendedSettingsButton;
private boolean mAttachedToWindow;
+ private final MediaRouteChooserContentManager mContentManager;
+
public MediaRouteChooserDialog(Context context, int theme) {
this(context, theme, true);
}
@@ -70,7 +68,7 @@ public class MediaRouteChooserDialog extends AlertDialog {
mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mCallback = new MediaRouterCallback();
- mShowProgressBarWhenEmpty = showProgressBarWhenEmpty;
+ mContentManager = new MediaRouteChooserContentManager(context, showProgressBarWhenEmpty);
}
/**
@@ -128,8 +126,9 @@ public class MediaRouteChooserDialog extends AlertDialog {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Note: setView must be called before super.onCreate().
- setView(LayoutInflater.from(getContext()).inflate(R.layout.media_route_chooser_dialog,
- null));
+ View containerView = LayoutInflater.from(getContext()).inflate(
+ R.layout.media_route_chooser_dialog, null);
+ setView(containerView);
setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
? R.string.media_route_chooser_title_for_remote_display
@@ -140,25 +139,15 @@ public class MediaRouteChooserDialog extends AlertDialog {
super.onCreate(savedInstanceState);
- View emptyView = findViewById(android.R.id.empty);
mAdapter = new RouteAdapter(getContext());
- mListView = (ListView) findViewById(R.id.media_route_list);
- mListView.setAdapter(mAdapter);
- mListView.setOnItemClickListener(mAdapter);
- mListView.setEmptyView(emptyView);
+ ListView listView = findViewById(R.id.media_route_list);
+ listView.setAdapter(mAdapter);
+ listView.setOnItemClickListener(mAdapter);
- mExtendedSettingsButton = (Button) findViewById(R.id.media_route_extended_settings_button);
+ mExtendedSettingsButton = findViewById(R.id.media_route_extended_settings_button);
updateExtendedSettingsButton();
- if (!mShowProgressBarWhenEmpty) {
- findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE);
-
- // Center the empty view when the progress bar is not shown.
- LinearLayout.LayoutParams params =
- (LinearLayout.LayoutParams) emptyView.getLayoutParams();
- params.gravity = Gravity.CENTER;
- emptyView.setLayoutParams(params);
- }
+ mContentManager.bindViews(containerView);
}
private void updateExtendedSettingsButton() {
@@ -240,8 +229,8 @@ public class MediaRouteChooserDialog extends AlertDialog {
view = mInflater.inflate(R.layout.media_route_list_item, parent, false);
}
MediaRouter.RouteInfo route = getItem(position);
- TextView text1 = (TextView)view.findViewById(android.R.id.text1);
- TextView text2 = (TextView)view.findViewById(android.R.id.text2);
+ TextView text1 = view.findViewById(android.R.id.text1);
+ TextView text2 = view.findViewById(android.R.id.text2);
text1.setText(route.getName());
CharSequence description = route.getDescription();
if (TextUtils.isEmpty(description)) {
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 325790c22fce..8393f8b4db61 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -290,7 +290,16 @@ message SystemSettingsProto {
optional SettingProto apply_ramping_ringer = 35 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ message Display {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto cv_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional Display display = 39;
+
+
+
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 39;
+ // Next tag = 40;
}
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 4e0cf753722f..732021c65742 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_call.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -32,11 +32,12 @@
android:orientation="vertical"
>
- <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/notification_2025_min_height"
android:clipChildren="false"
+ android:layout_weight="1"
>
<ImageView
@@ -170,7 +171,7 @@
android:layout_height="@dimen/notification_close_button_size"
android:layout_gravity="top|end" />
- </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+ </FrameLayout>
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
index 5783201981a8..1ee7ddc8d060 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
@@ -32,12 +32,12 @@
android:orientation="vertical"
>
-
- <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/notification_2025_min_height"
android:clipChildren="false"
+ android:layout_weight="1"
>
<ImageView
@@ -194,7 +194,7 @@
android:layout_height="@dimen/notification_close_button_size"
android:layout_gravity="top|end" />
- </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+ </FrameLayout>
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index 6391b1ebf744..af660254d172 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -35,11 +35,12 @@
>
- <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/notification_2025_min_height"
android:clipChildren="false"
+ android:layout_weight="1"
>
<ImageView
@@ -204,7 +205,7 @@
android:layout_height="@dimen/notification_close_button_size"
android:layout_gravity="top|end" />
- </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+ </FrameLayout>
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
diff --git a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
index e8b295bd5fdb..0287e6c086aa 100644
--- a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
@@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.hardware.usb.UsbDevice;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -118,6 +118,6 @@ public class BrailleDisplayControllerImplTest {
verify(mBrailleDisplayCallback).onConnectionFailed(
BrailleDisplayController.BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS);
- verifyZeroInteractions(mAccessibilityServiceConnection);
+ verifyNoMoreInteractions(mAccessibilityServiceConnection);
}
}
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index dccbf4036b3e..70b4150115fe 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -233,7 +233,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
/**
* Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
*/
- private class TestServicesCache extends RegisteredServicesCache<TestServiceType> {
+ public class TestServicesCache extends RegisteredServicesCache<TestServiceType> {
static final String SERVICE_INTERFACE = "RegisteredServicesCacheTest";
static final String SERVICE_META_DATA = "RegisteredServicesCacheTest";
static final String ATTRIBUTES_NAME = "test";
@@ -245,12 +245,6 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer());
}
- TestServicesCache(Injector<TestServiceType> injector,
- XmlSerializerAndParser<TestServiceType> serializerAndParser) {
- super(injector, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME,
- serializerAndParser);
- }
-
@Override
public TestServiceType parseServiceAttributes(Resources res, String packageName,
AttributeSet attrs) {
@@ -338,7 +332,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
}
}
- static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {
+ public static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {
public void writeAsXml(TestServiceType item, TypedXmlSerializer out) throws IOException {
out.attribute(null, "type", item.type);
@@ -353,7 +347,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
}
}
- static class TestServiceType implements Parcelable {
+ public static class TestServiceType implements Parcelable {
final String type;
final String value;
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java
new file mode 100644
index 000000000000..8349659517c5
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.content.pm.Flags.FLAG_OPTIMIZE_PARSING_IN_REGISTERED_SERVICES_CACHE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.RegisteredServicesCacheTest.TestServiceType;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.content.pm.RegisteredServicesCache}
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_OPTIMIZE_PARSING_IN_REGISTERED_SERVICES_CACHE)
+public class RegisteredServicesCacheUnitTest {
+ private static final String TAG = "RegisteredServicesCacheUnitTest";
+ private static final int U0 = 0;
+ private static final int U1 = 1;
+ private static final int UID1 = 1;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private final ResolveInfo mResolveInfo1 = new ResolveInfo();
+ private final ResolveInfo mResolveInfo2 = new ResolveInfo();
+ private final TestServiceType mTestServiceType1 = new TestServiceType("t1", "value1");
+ private final TestServiceType mTestServiceType2 = new TestServiceType("t2", "value2");
+ @Mock
+ RegisteredServicesCache.Injector<TestServiceType> mMockInjector;
+ @Mock
+ Context mMockContext;
+ Handler mMockBackgroundHandler;
+ @Mock
+ PackageManager mMockPackageManager;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockInjector.getContext()).thenReturn(mMockContext);
+ mMockBackgroundHandler = spy(BackgroundThread.getHandler());
+ when(mMockInjector.getBackgroundHandler()).thenReturn(mMockBackgroundHandler);
+ doReturn(mock(Intent.class)).when(mMockContext).registerReceiverAsUser(any(), any(), any(),
+ any(), any());
+ doReturn(mock(Intent.class)).when(mMockContext).registerReceiver(any(), any(), any(),
+ any());
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+ addServiceInfoIntoResolveInfo(mResolveInfo1, "r1.package.name" /* packageName */,
+ "r1.service.name" /* serviceName */);
+ addServiceInfoIntoResolveInfo(mResolveInfo2, "r2.package.name" /* packageName */,
+ "r2.service.name" /* serviceName */);
+ }
+
+ @Test
+ public void testSaveServiceInfoIntoCaches() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+ PackageInfo packageInfo2 = createPackageInfo(2000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo2.serviceInfo.packageName),
+ anyInt(), eq(U1))).thenReturn(packageInfo2);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ int u1uid = UserHandle.getUid(U1, UID1);
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo2 = newServiceInfo(
+ mTestServiceType2, u1uid, mResolveInfo2.serviceInfo.getComponentName(),
+ 2000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U1, mResolveInfo2, serviceInfo2);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ testServicesCache.getAllServices(U1);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
+
+ reset(testServicesCache);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.invalidateCache(U1);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ testServicesCache.getAllServices(U1);
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
+ }
+
+ @Test
+ public void testClearServiceInfoCachesAfterRemoveUserId() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+
+ testServicesCache.onUserRemoved(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ }
+
+ @Test
+ public void testGetServiceInfoCachesForMultiUser() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U1))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+
+ testServicesCache.clearServicesForQuerying();
+ int u1uid = UserHandle.getUid(U1, UID1);
+ assertThat(u1uid).isNotEqualTo(UID1);
+
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo2 = newServiceInfo(
+ mTestServiceType1, u1uid, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U1, mResolveInfo1, serviceInfo2);
+
+ testServicesCache.getAllServices(U1);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.invalidateCache(U1);
+
+ // There is a bug to return the same info from the cache for different users. Make sure it
+ // will return the different info from the cache for different users.
+ Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> serviceInfos;
+ serviceInfos = testServicesCache.getAllServices(U0);
+ // Make sure the service info is retrieved from the cache for U0.
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ for (RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo : serviceInfos) {
+ assertThat(serviceInfo.componentInfo.applicationInfo.uid).isEqualTo(UID1);
+ }
+
+ serviceInfos = testServicesCache.getAllServices(U1);
+ // Make sure the service info is retrieved from the cache for U1.
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
+ for (RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo : serviceInfos) {
+ assertThat(serviceInfo.componentInfo.applicationInfo.uid).isEqualTo(u1uid);
+ }
+ }
+
+ @Test
+ public void testUpdateServiceInfoIntoCachesWhenPackageInfoNotFound() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+ reset(mMockPackageManager);
+
+ doThrow(new SecurityException("")).when(mMockPackageManager).getPackageInfoAsUser(
+ eq(mResolveInfo1.serviceInfo.packageName), anyInt(), eq(U0));
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), anyLong());
+ }
+
+ @Test
+ public void testUpdateServiceInfoIntoCachesWhenTheApplicationHasBeenUpdated() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+ reset(mMockPackageManager);
+
+ PackageInfo packageInfo2 = createPackageInfo(2000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo2);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(2000L));
+ }
+
+ @Test
+ public void testClearServiceInfoCachesAfterTimeout() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ // Immediately invoke run on the Runnable posted to the handler
+ doAnswer(invocation -> {
+ Message message = invocation.getArgument(0);
+ message.getCallback().run();
+ return true;
+ }).when(mMockBackgroundHandler).sendMessageAtTime(any(Message.class), anyLong());
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ verify(mMockBackgroundHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
+
+ reset(testServicesCache);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ }
+
+ private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
+ TestServiceType type, int uid, ComponentName componentName, long lastUpdateTime) {
+ final ComponentInfo info = new ComponentInfo();
+ info.applicationInfo = new ApplicationInfo();
+ info.applicationInfo.uid = uid;
+ return new RegisteredServicesCache.ServiceInfo<>(type, info, componentName, lastUpdateTime);
+ }
+
+ private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+ String serviceName) {
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = packageName;
+ serviceInfo.name = serviceName;
+ resolveInfo.serviceInfo = serviceInfo;
+ }
+
+ private PackageInfo createPackageInfo(long lastUpdateTime) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.lastUpdateTime = lastUpdateTime;
+ return packageInfo;
+ }
+
+ /**
+ * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
+ */
+ public class TestRegisteredServicesCache extends RegisteredServicesCache<TestServiceType> {
+ static final String SERVICE_INTERFACE = "RegisteredServicesCacheUnitTest";
+ static final String SERVICE_META_DATA = "RegisteredServicesCacheUnitTest";
+ static final String ATTRIBUTES_NAME = "test";
+ private SparseArray<Map<ResolveInfo, ServiceInfo<TestServiceType>>> mServices =
+ new SparseArray<>();
+
+ public TestRegisteredServicesCache(Injector<TestServiceType> injector,
+ XmlSerializerAndParser<TestServiceType> serializerAndParser) {
+ super(injector, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME,
+ serializerAndParser);
+ }
+
+ @Override
+ public TestServiceType parseServiceAttributes(Resources res, String packageName,
+ AttributeSet attrs) {
+ return null;
+ }
+
+ @Override
+ protected List<ResolveInfo> queryIntentServices(int userId) {
+ Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId,
+ new HashMap<ResolveInfo, ServiceInfo<TestServiceType>>());
+ return new ArrayList<>(map.keySet());
+ }
+
+ void addServiceForQuerying(int userId, ResolveInfo resolveInfo,
+ ServiceInfo<TestServiceType> serviceInfo) {
+ Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId);
+ if (map == null) {
+ map = new HashMap<>();
+ mServices.put(userId, map);
+ }
+ map.put(resolveInfo, serviceInfo);
+ }
+
+ void clearServicesForQuerying() {
+ mServices.clear();
+ }
+
+ @Override
+ protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
+ long lastUpdateTime) throws XmlPullParserException, IOException {
+ int size = mServices.size();
+ for (int i = 0; i < size; i++) {
+ Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
+ ServiceInfo<TestServiceType> serviceInfo = map.get(resolveInfo);
+ if (serviceInfo != null) {
+ return serviceInfo;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected service " + resolveInfo);
+ }
+
+ @Override
+ public void onUserRemoved(int userId) {
+ super.onUserRemoved(userId);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index de5f0ffbe23f..34650be331d3 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -264,7 +264,7 @@ public class DisplayManagerGlobalTest {
/* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
- Mockito.verifyZeroInteractions(mDisplayListener);
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
@@ -272,7 +272,7 @@ public class DisplayManagerGlobalTest {
/* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
waitForHandler();
- Mockito.verifyZeroInteractions(mDisplayListener);
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
@@ -280,7 +280,7 @@ public class DisplayManagerGlobalTest {
/* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
- Mockito.verifyZeroInteractions(mDisplayListener);
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
}
@Test
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 8ac9292390b0..50cd4c00c2ce 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -29,7 +29,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.os.CancellationSignal;
@@ -230,7 +230,7 @@ public class PendingInsetsControllerTest {
InsetsController secondController = mock(InsetsController.class);
mPendingInsetsController.replayAndAttach(secondController);
verify(mReplayedController).show(eq(systemBars()));
- verifyZeroInteractions(secondController);
+ verifyNoMoreInteractions(secondController);
}
@Test
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index eb482f2e0aa5..f811d8efedeb 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.MockitoAnnotations.initMocks;
import android.os.Bundle;
@@ -74,7 +74,7 @@ public class AccessibilityInteractionClientTest {
MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
assertEquals("Node got lost along the way", nodeFromConnection, node);
- verifyZeroInteractions(mMockCache);
+ verifyNoMoreInteractions(mMockCache);
}
@Test
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index 5f89f9c14793..8bbe81da512b 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -27,7 +27,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
@@ -130,7 +130,7 @@ public class MainContentCaptureSessionTest {
mTestableLooper.processAllMessages();
assertThat(session.mContentProtectionEventProcessor).isNull();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
}
@Test
@@ -151,7 +151,7 @@ public class MainContentCaptureSessionTest {
mTestableLooper.processAllMessages();
assertThat(session.mContentProtectionEventProcessor).isNull();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
}
@Test
@@ -172,7 +172,7 @@ public class MainContentCaptureSessionTest {
mTestableLooper.processAllMessages();
assertThat(session.mContentProtectionEventProcessor).isNull();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
}
@Test
@@ -197,7 +197,7 @@ public class MainContentCaptureSessionTest {
session.sendEvent(EVENT);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
@@ -227,7 +227,7 @@ public class MainContentCaptureSessionTest {
session.sendEvent(EVENT);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNotNull();
assertThat(session.mEvents).containsExactly(EVENT);
}
@@ -255,7 +255,7 @@ public class MainContentCaptureSessionTest {
session.sendEvent(EVENT);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
@@ -272,8 +272,8 @@ public class MainContentCaptureSessionTest {
session.flush(REASON);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
- verifyZeroInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
assertThat(session.mEvents).containsExactly(EVENT);
}
@@ -289,8 +289,8 @@ public class MainContentCaptureSessionTest {
session.flush(REASON);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
- verifyZeroInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
assertThat(session.mEvents).containsExactly(EVENT);
}
@@ -307,7 +307,7 @@ public class MainContentCaptureSessionTest {
session.flush(REASON);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isEmpty();
assertEventFlushedContentCapture(options);
}
@@ -325,7 +325,7 @@ public class MainContentCaptureSessionTest {
session.flush(REASON);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isEmpty();
assertEventFlushedContentCapture(options);
}
@@ -339,7 +339,7 @@ public class MainContentCaptureSessionTest {
mTestableLooper.processAllMessages();
verify(mMockSystemServerInterface).finishSession(anyInt());
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mDirectServiceInterface).isNull();
assertThat(session.mContentProtectionEventProcessor).isNull();
}
@@ -352,8 +352,8 @@ public class MainContentCaptureSessionTest {
session.resetSession(/* newState= */ 0);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockSystemServerInterface);
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockSystemServerInterface);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mDirectServiceInterface).isNull();
assertThat(session.mContentProtectionEventProcessor).isNull();
}
@@ -370,8 +370,8 @@ public class MainContentCaptureSessionTest {
notifyContentCaptureEvents(session);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentCaptureDirectManager);
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
@@ -388,8 +388,8 @@ public class MainContentCaptureSessionTest {
notifyContentCaptureEvents(session);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentCaptureDirectManager);
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
@@ -407,8 +407,8 @@ public class MainContentCaptureSessionTest {
notifyContentCaptureEvents(session);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentCaptureDirectManager);
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
index ba0dbf454ad2..e75452cafdab 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -443,7 +442,7 @@ public class ContentProtectionEventProcessorTest {
mTestLooper.dispatchAll();
verify(mMockEventBuffer, never()).clear();
verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
+ verifyNoMoreInteractions(mMockContentCaptureManager);
}
private void assertLoginDetected() throws Exception {
diff --git a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
index b61d86819c17..3570c2e0ace0 100644
--- a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
@@ -33,7 +33,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.app.Activity;
import android.app.Instrumentation;
@@ -159,7 +158,7 @@ public class TextViewReceiveContentTest {
ContentInfo payload =
new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
}
@Test
@@ -180,19 +179,19 @@ public class TextViewReceiveContentTest {
ContentInfo payload =
new ContentInfo.Builder(clip, SOURCE_CLIPBOARD).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
payload = new ContentInfo.Builder(clip, SOURCE_INPUT_METHOD).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
payload = new ContentInfo.Builder(clip, SOURCE_DRAG_AND_DROP).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
payload = new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
}
private static class MyInputConnection extends InputConnectionWrapper {
diff --git a/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt b/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt
index 381b566018c7..ad68e385459e 100644
--- a/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt
+++ b/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt
@@ -37,7 +37,7 @@ class BackTouchTrackerTest {
fun generatesProgress_onStart() {
val linearTracker = linearTouchTracker()
linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
- val event = linearTracker.createStartEvent(null)
+ val event = linearTracker.createStartEvent()
assertEquals(0f, event.progress, 0f)
}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 215c1623a530..66524d1c1d2a 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -695,8 +695,7 @@ public class WindowOnBackInvokedDispatcherTest {
/* frameTimeMillis = */ 0,
/* progress = */ progress,
/* triggerBack = */ false,
- /* swipeEdge = */ BackEvent.EDGE_LEFT,
- /* departingAnimationTarget = */ null);
+ /* swipeEdge = */ BackEvent.EDGE_LEFT);
}
private void verifyImeCallackRegistrations() throws RemoteException {
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 6a1a633eb6d0..1977ff52c7c5 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -20,6 +20,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHO
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
@@ -43,7 +44,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -521,7 +522,7 @@ public class AccessibilityShortcutControllerTest {
AccessibilityShortcutController.DialogStatus.SHOWN);
getController().performAccessibilityShortcut();
- verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
+ verifyNoMoreInteractions(mAlertDialogBuilder, mAlertDialog);
verify(mToast).show();
verify(mAccessibilityManagerService).performAccessibilityShortcut(
Display.DEFAULT_DISPLAY, HARDWARE, null);
@@ -614,7 +615,7 @@ public class AccessibilityShortcutControllerTest {
AccessibilityShortcutController.DialogStatus.SHOWN);
getController().performAccessibilityShortcut();
- verifyZeroInteractions(mToast);
+ verifyNoMoreInteractions(mToast);
verify(mAccessibilityManagerService).performAccessibilityShortcut(
Display.DEFAULT_DISPLAY, HARDWARE, null);
}
@@ -631,7 +632,7 @@ public class AccessibilityShortcutControllerTest {
AccessibilityShortcutController.DialogStatus.SHOWN);
getController().performAccessibilityShortcut();
- verifyZeroInteractions(mToast);
+ verifyNoMoreInteractions(mToast);
verify(mAccessibilityManagerService).performAccessibilityShortcut(
Display.DEFAULT_DISPLAY, HARDWARE, null);
}
@@ -714,6 +715,25 @@ public class AccessibilityShortcutControllerTest {
verify(mRingtone, times(0)).play();
}
+ @Test
+ public void onUserSetupComplete_noEnabledServices_blankHardwareSetting() throws Exception {
+ AccessibilityShortcutController controller = getController();
+ configureValidShortcutService();
+ // Shortcut setting should be cleared on user setup
+ Settings.Secure.putStringForUser(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, 0);
+ when(mAccessibilityManagerService
+ .getEnabledAccessibilityServiceList(anyInt(), eq(0)))
+ .thenReturn(Collections.emptyList());
+ Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 1);
+
+ controller.mUserSetupCompleteObserver.onChange(true);
+
+ final String shortcut = Settings.Secure.getStringForUser(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, 0);
+ assertThat(shortcut).isEqualTo("");
+ }
+
private void configureNoShortcutService() throws Exception {
when(mAccessibilityManagerService
.getAccessibilityShortcutTargets(HARDWARE))
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index d21ab44d251d..15e746cb13b6 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -87,6 +87,10 @@ public class ResolverActivityTest {
private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
.getInstrumentation().getTargetContext().getUser();
+ private static final int WORK_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 1;
+ private static final int CLONE_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 2;
+ private static final int PRIVATE_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 3;
+
@Rule
public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
new ActivityTestRule<>(ResolverWrapperActivity.class, false,
@@ -247,7 +251,7 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID,
PERSONAL_USER_HANDLE);
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
@@ -270,7 +274,7 @@ public class ResolverActivityTest {
};
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10,
+ createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID,
PERSONAL_USER_HANDLE);
// We pick the first one as there is another one in the work profile side
onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
@@ -444,7 +448,7 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
@@ -456,7 +460,7 @@ public class ResolverActivityTest {
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+ assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
// The work list adapter must be populated in advance before tapping the other tab
assertThat(activity.getWorkListAdapter().getCount(), is(4));
}
@@ -466,7 +470,7 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
@@ -478,7 +482,7 @@ public class ResolverActivityTest {
waitForIdle();
onView(withText(R.string.resolver_work_tab)).perform(click());
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(WORK_USER_ID));
assertThat(activity.getWorkListAdapter().getCount(), is(4));
}
@@ -498,7 +502,7 @@ public class ResolverActivityTest {
waitForIdle();
onView(withText(R.string.resolver_work_tab)).perform(click());
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(WORK_USER_ID));
assertThat(activity.getPersonalListAdapter().getCount(), is(2));
}
@@ -508,7 +512,7 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
sOverrides.workProfileUserHandle);
@@ -530,7 +534,7 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
sOverrides.workProfileUserHandle);
@@ -633,7 +637,7 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
sOverrides.workProfileUserHandle);
@@ -669,7 +673,7 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets,
@@ -697,7 +701,7 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets,
@@ -844,7 +848,7 @@ public class ResolverActivityTest {
public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() {
ResolverActivity.ENABLE_TABBED_VIEW = false;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID,
PERSONAL_USER_HANDLE);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -898,7 +902,7 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets,
@@ -1376,15 +1380,16 @@ public class ResolverActivityTest {
}
private void markWorkProfileUserAvailable() {
- ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
+ ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(WORK_USER_ID);
}
private void markCloneProfileUserAvailable() {
- ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
+ ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(CLONE_USER_ID);
}
private void markPrivateProfileUserAvailable() {
- ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
+ ResolverWrapperActivity.sOverrides.privateProfileUserHandle =
+ UserHandle.of(PRIVATE_USER_ID);
}
private void setTabOwnerUserHandleForLaunch(UserHandle tabOwnerUserHandleForLaunch) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 4604b01d1bd2..050a68a89d6f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -112,8 +112,8 @@ public class ResolverWrapperActivity extends ResolverActivity {
@Override
protected ResolverListController createListController(UserHandle userHandle) {
- if (userHandle == UserHandle.SYSTEM) {
- when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+ if (userHandle == getUser()) {
+ when(sOverrides.resolverListController.getUserHandle()).thenReturn(getUser());
return sOverrides.resolverListController;
}
if (isLaunchedInSingleUserMode()) {
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 17fe15c94294..21ef391ee9de 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -28,7 +28,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.annotation.EnforcePermission;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
@@ -207,7 +207,7 @@ public final class DeviceStateManagerGlobalTest {
mService.setSupportedStates(List.of(OTHER_DEVICE_STATE));
mService.setBaseState(OTHER_DEVICE_STATE);
- verifyZeroInteractions(callback);
+ verifyNoMoreInteractions(callback);
}
@Test
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 592c7cdd070c..b6a1501831c0 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -114,7 +114,7 @@ flag {
name: "enable_shell_top_task_tracking"
namespace: "multitasking"
description: "Enables tracking top tasks from the shell"
- bug: "342627272"
+ bug: "346588978"
metadata {
purpose: PURPOSE_BUGFIX
}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index e11babe5cb0e..ed8b54397076 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -22,8 +22,8 @@
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
- android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation"
- android:paddingEnd="@dimen/desktop_mode_handle_menu_pill_elevation"
+ android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation_padding"
+ android:paddingEnd="@dimen/desktop_mode_handle_menu_pill_elevation_padding"
android:orientation="vertical">
<LinearLayout
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 9ebbf71138b0..2a8c88ed3a40 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -525,17 +525,21 @@
<!-- The radius of the Maximize menu shadow. -->
<dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
- <!-- The width of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_width">216dp</dimen>
+ <!-- The width of the handle menu in desktop mode plus the 2dp added for padding to account for
+ pill elevation. -->
+ <dimen name="desktop_mode_handle_menu_width">218dp</dimen>
- <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each,
- additional actions pill 208dp, plus 2dp spacing between them plus 4dp top padding.
- 52*3 + 52*4 + (4-1)*2 + 4 = 374 -->
- <dimen name="desktop_mode_handle_menu_height">374dp</dimen>
+ <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each plus
+ additional actions pill 208dp plus 2dp spacing between them plus 4dp top padding
+ plus 2dp bottom padding: 52*3 + 52*4 + (4-1)*2 + 4 + 2 = 376 -->
+ <dimen name="desktop_mode_handle_menu_height">376dp</dimen>
<!-- The elevation set on the handle menu pills. -->
<dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
+ <!-- The padding added to account for the handle menu's pills' elevation. -->
+ <dimen name="desktop_mode_handle_menu_pill_elevation_padding">2dp</dimen>
+
<!-- The height of the handle menu's "App Info" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen>
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 b87c2054bea6..e145ea9dfa8f 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
@@ -23,6 +23,7 @@ import android.content.pm.PackageManager
import android.window.DesktopModeFlags
import com.android.internal.R
import com.android.internal.policy.DesktopModeCompatUtils
+import java.util.function.Supplier
/**
* Class to decide whether to apply app compat policies in desktop mode.
@@ -34,9 +35,11 @@ class DesktopModeCompatPolicy(private val context: Context) {
private val pkgManager: PackageManager
get() = context.getPackageManager()
private val defaultHomePackage: String?
- get() = pkgManager.getHomeActivities(ArrayList())?.packageName
+ get() = defaultHomePackageSupplier?.get()
+ ?: pkgManager.getHomeActivities(ArrayList())?.packageName
private val packageInfoCache = mutableMapOf<String, Boolean>()
+ var defaultHomePackageSupplier: Supplier<String?>? = null
/**
* If the top activity should be exempt from desktop windowing and forced back to fullscreen.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 53dede6bd227..7f8cfaeb9c03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -448,7 +448,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (!shouldDispatchToAnimator && mActiveCallback != null) {
mCurrentTracker.updateStartLocation();
- tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
+ tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent());
if (mBackNavigationInfo != null && !isAppProgressGenerationAllowed()) {
tryPilferPointers();
}
@@ -604,7 +604,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
// App is handling back animation. Cancel system animation latency tracking.
cancelLatencyTracking();
- tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
+ tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent());
if (!isAppProgressGenerationAllowed()) {
tryPilferPointers();
}
@@ -1041,7 +1041,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
() -> mShellExecutor.execute(this::onBackAnimationFinished));
if (mApps.length >= 1) {
- BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
+ BackMotionEvent startEvent = mCurrentTracker.createStartEvent();
dispatchOnBackStarted(mActiveCallback, startEvent);
if (startEvent.getSwipeEdge() == EDGE_NONE) {
// TODO(b/373544911): onBackStarted is dispatched here so that
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index efc952644f0b..912de813cf59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2644,7 +2644,8 @@ public class BubbleController implements ConfigurationChangeListener,
}
private void moveBubbleToFullscreen(String key) {
- // TODO b/388858013: convert the bubble to full screen
+ Bubble b = mBubbleData.getBubbleInStackWithKey(key);
+ mBubbleTransitions.startDraggedBubbleIconToFullscreen(b);
}
private boolean isDeviceLocked() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 51a5b12edb84..8cd6ce020408 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -113,6 +113,11 @@ public class BubbleTransitions {
return convert;
}
+ /** Starts a transition that converts a dragged bubble icon to a full screen task. */
+ public BubbleTransition startDraggedBubbleIconToFullscreen(Bubble bubble) {
+ return new DraggedBubbleIconToFullscreen(bubble);
+ }
+
/**
* Plucks the task-surface out of an ancestor view while making the view invisible. This helper
* attempts to do this seamlessly (ie. view becomes invisible in sync with task reparent).
@@ -645,4 +650,115 @@ public class BubbleTransitions {
t.apply();
}
}
+
+ /**
+ * A transition that converts a dragged bubble icon to a full screen window.
+ *
+ * <p>This transition assumes that the bubble is invisible so it is simply sent to front.
+ */
+ class DraggedBubbleIconToFullscreen implements Transitions.TransitionHandler, BubbleTransition {
+
+ IBinder mTransition;
+ final Bubble mBubble;
+
+ DraggedBubbleIconToFullscreen(Bubble bubble) {
+ mBubble = bubble;
+ bubble.setPreparingTransition(this);
+ WindowContainerToken token = bubble.getTaskView().getTaskInfo().getToken();
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setAlwaysOnTop(token, false);
+ wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED);
+ wct.reorder(token, /* onTop= */ true);
+ wct.setHidden(token, false);
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false);
+ mTaskViewTransitions.enqueueExternal(bubble.getTaskView().getController(), () -> {
+ mTransition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, this);
+ return mTransition;
+ });
+ }
+
+ @Override
+ public void skip() {
+ mBubble.setPreparingTransition(null);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mTransition != transition) {
+ return false;
+ }
+
+ final TaskViewTaskController taskViewTaskController =
+ mBubble.getTaskView().getController();
+ if (taskViewTaskController == null) {
+ mTaskViewTransitions.onExternalDone(transition);
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ TransitionInfo.Change change = findTransitionChange(info);
+ if (change == null) {
+ Slog.w(TAG, "Expected a TaskView transition to front but didn't find "
+ + "one, cleaning up the task view");
+ taskViewTaskController.setTaskNotFound();
+ mTaskViewTransitions.onExternalDone(transition);
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+ mRepository.remove(taskViewTaskController);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ taskViewTaskController.notifyTaskRemovalStarted(mBubble.getTaskView().getTaskInfo());
+ mTaskViewTransitions.onExternalDone(transition);
+ return true;
+ }
+
+ private TransitionInfo.Change findTransitionChange(TransitionInfo info) {
+ TransitionInfo.Change result = null;
+ WindowContainerToken token = mBubble.getTaskView().getTaskInfo().getToken();
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() == null) {
+ continue;
+ }
+ if (change.getMode() != TRANSIT_TO_FRONT) {
+ continue;
+ }
+ if (!token.equals(change.getTaskInfo().token)) {
+ continue;
+ }
+ result = change;
+ break;
+ }
+ return result;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ if (!aborted) {
+ return;
+ }
+ mTransition = null;
+ mTaskViewTransitions.onExternalDone(transition);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index c4696d5f44f4..a8e6b593f20d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -20,17 +20,14 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL;
import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.os.RemoteException;
import android.util.Slog;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.shared.TransactionPool;
-import com.android.wm.shell.transition.LegacyTransitions;
import java.util.ArrayList;
@@ -87,25 +84,6 @@ public final class SyncTransactionQueue {
}
/**
- * Queues a legacy transition to be sent serially to WM
- */
- public void queue(LegacyTransitions.ILegacyTransition transition,
- @WindowManager.TransitionType int type, WindowContainerTransaction wct) {
- if (wct.isEmpty()) {
- if (DEBUG) Slog.d(TAG, "Skip queue due to transaction change is empty");
- return;
- }
- SyncCallback cb = new SyncCallback(transition, type, wct);
- synchronized (mQueue) {
- if (DEBUG) Slog.d(TAG, "Queueing up legacy transition " + wct);
- mQueue.add(cb);
- if (mQueue.size() == 1) {
- cb.send();
- }
- }
- }
-
- /**
* Queues a sync transaction only if there are already sync transaction(s) queued or in flight.
* Otherwise just returns without queueing.
* @return {@code true} if queued, {@code false} if not.
@@ -168,17 +146,9 @@ public final class SyncTransactionQueue {
private class SyncCallback extends WindowContainerTransactionCallback {
int mId = -1;
final WindowContainerTransaction mWCT;
- final LegacyTransitions.LegacyTransition mLegacyTransition;
SyncCallback(WindowContainerTransaction wct) {
mWCT = wct;
- mLegacyTransition = null;
- }
-
- SyncCallback(LegacyTransitions.ILegacyTransition legacyTransition,
- @WindowManager.TransitionType int type, WindowContainerTransaction wct) {
- mWCT = wct;
- mLegacyTransition = new LegacyTransitions.LegacyTransition(type, legacyTransition);
}
// Must be sychronized on mQueue
@@ -194,12 +164,7 @@ public final class SyncTransactionQueue {
}
if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
try {
- if (mLegacyTransition != null) {
- mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
- mLegacyTransition.getAdapter(), this, mWCT);
- } else {
- mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
- }
+ mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
} catch (RuntimeException e) {
Slog.e(TAG, "Send failed", e);
// Finish current sync callback immediately.
@@ -228,18 +193,10 @@ public final class SyncTransactionQueue {
if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId);
mQueue.remove(this);
onTransactionReceived(t);
- if (mLegacyTransition != null) {
- try {
- mLegacyTransition.getSyncCallback().onTransactionReady(mId, t);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e);
- }
- } else {
- ProtoLog.v(WM_SHELL,
- "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id);
- t.apply();
- t.close();
- }
+ ProtoLog.v(WM_SHELL,
+ "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id);
+ t.apply();
+ t.close();
if (!mQueue.isEmpty()) {
mQueue.get(0).send();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
index 453ca167557a..1128fb2259b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
@@ -86,8 +86,7 @@ public class PipDesktopState {
return false;
}
final int displayId = mPipDisplayLayoutState.getDisplayId();
- return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(displayId)
- || isDisplayInFreeform();
+ return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(displayId);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
index 4cbb78f2dae2..d36201a4ac9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
@@ -52,24 +52,15 @@ public class PipDoubleTapHelper {
public static final int SIZE_SPEC_MAX = 1;
public static final int SIZE_SPEC_CUSTOM = 2;
- /**
- * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
- *
- * <p>Each double tap toggles back and forth between {@code PipSizeSpec.CUSTOM} and
- * either {@code PipSizeSpec.MAX} or {@code PipSizeSpec.DEFAULT}. The choice between
- * the latter two sizes is determined based on the current state of the pip screen.</p>
- *
- * @param mPipBoundsState current state of the pip screen
- */
@PipSizeSpec
- private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState mPipBoundsState) {
+ private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState pipBoundsState) {
// determine the average pip screen width
- int averageWidth = (mPipBoundsState.getMaxSize().x
- + mPipBoundsState.getMinSize().x) / 2;
+ int averageWidth = (pipBoundsState.getMaxSize().x
+ + pipBoundsState.getMinSize().x) / 2;
// If pip screen width is above average, DEFAULT is the size spec we need to
// toggle to. Otherwise, we choose MAX.
- return (mPipBoundsState.getBounds().width() > averageWidth)
+ return (pipBoundsState.getBounds().width() > averageWidth)
? SIZE_SPEC_DEFAULT
: SIZE_SPEC_MAX;
}
@@ -77,35 +68,33 @@ public class PipDoubleTapHelper {
/**
* Determines the {@link PipSizeSpec} to toggle to on double tap.
*
- * @param mPipBoundsState current state of the pip screen
+ * @param pipBoundsState current state of the pip bounds
* @param userResizeBounds latest user resized bounds (by pinching in/out)
- * @return pip screen size to switch to
*/
@PipSizeSpec
- public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+ public static int nextSizeSpec(@NonNull PipBoundsState pipBoundsState,
@NonNull Rect userResizeBounds) {
- // is pip screen at its maximum
- boolean isScreenMax = mPipBoundsState.getBounds().width()
- == mPipBoundsState.getMaxSize().x;
-
- // is pip screen at its normal default size
- boolean isScreenDefault = (mPipBoundsState.getBounds().width()
- == mPipBoundsState.getNormalBounds().width())
- && (mPipBoundsState.getBounds().height()
- == mPipBoundsState.getNormalBounds().height());
+ boolean isScreenMax = pipBoundsState.getBounds().width() == pipBoundsState.getMaxSize().x
+ && pipBoundsState.getBounds().height() == pipBoundsState.getMaxSize().y;
+ boolean isScreenDefault = (pipBoundsState.getBounds().width()
+ == pipBoundsState.getNormalBounds().width())
+ && (pipBoundsState.getBounds().height()
+ == pipBoundsState.getNormalBounds().height());
// edge case 1
// if user hasn't resized screen yet, i.e. CUSTOM size does not exist yet
// or if user has resized exactly to DEFAULT, then we just want to maximize
if (isScreenDefault
- && userResizeBounds.width() == mPipBoundsState.getNormalBounds().width()) {
+ && userResizeBounds.width() == pipBoundsState.getNormalBounds().width()
+ && userResizeBounds.height() == pipBoundsState.getNormalBounds().height()) {
return SIZE_SPEC_MAX;
}
// edge case 2
- // if user has maximized, then we want to toggle to DEFAULT
+ // if user has resized to max, then we want to toggle to DEFAULT
if (isScreenMax
- && userResizeBounds.width() == mPipBoundsState.getMaxSize().x) {
+ && userResizeBounds.width() == pipBoundsState.getMaxSize().x
+ && userResizeBounds.height() == pipBoundsState.getMaxSize().y) {
return SIZE_SPEC_DEFAULT;
}
@@ -113,9 +102,6 @@ public class PipDoubleTapHelper {
if (isScreenDefault || isScreenMax) {
return SIZE_SPEC_CUSTOM;
}
-
- // if we are currently in user resized CUSTOM size state
- // then we toggle either to MAX or DEFAULT depending on the current pip screen state
- return getMaxOrDefaultPipSizeSpec(mPipBoundsState);
+ return getMaxOrDefaultPipSizeSpec(pipBoundsState);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 318cdeec5bc1..f62fd819319e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -95,6 +95,7 @@ import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.common.DefaultHomePackageSupplier;
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
@@ -260,8 +261,14 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static DesktopModeCompatPolicy provideDesktopModeCompatPolicy(Context context) {
- return new DesktopModeCompatPolicy(context);
+ static DesktopModeCompatPolicy provideDesktopModeCompatPolicy(
+ Context context,
+ ShellInit shellInit,
+ @ShellMainThread Handler mainHandler) {
+ final DesktopModeCompatPolicy policy = new DesktopModeCompatPolicy(context);
+ policy.setDefaultHomePackageSupplier(new DefaultHomePackageSupplier(
+ context, shellInit, mainHandler));
+ return policy;
}
@WMSingleton
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 179f03b8a975..257e27a338be 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
@@ -98,7 +98,6 @@ import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeMoveToDisplayTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
-import com.android.wm.shell.desktopmode.DesktopPipTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
@@ -781,7 +780,6 @@ public abstract class WMShellModule {
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
Optional<DesksTransitionObserver> desksTransitionObserver,
- Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy,
DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
@@ -825,7 +823,6 @@ public abstract class WMShellModule {
overviewToDesktopTransitionObserver,
desksOrganizer,
desksTransitionObserver.get(),
- desktopPipTransitionObserver.get(),
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
@@ -1228,7 +1225,6 @@ public abstract class WMShellModule {
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
- Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
ShellInit shellInit) {
@@ -1241,7 +1237,6 @@ public abstract class WMShellModule {
transitions,
shellTaskOrganizer,
desktopMixedTransitionHandler.get(),
- desktopPipTransitionObserver.get(),
backAnimationController.get(),
desktopWallpaperActivityTokenProvider,
shellInit)));
@@ -1263,19 +1258,6 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static Optional<DesktopPipTransitionObserver> provideDesktopPipTransitionObserver(
- Context context
- ) {
- if (DesktopModeStatus.canEnterDesktopMode(context)
- && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) {
- return Optional.of(
- new DesktopPipTransitionObserver());
- }
- return Optional.empty();
- }
-
- @WMSingleton
- @Provides
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
@@ -1492,6 +1474,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
InputManager inputManager,
+ DisplayController displayController,
@ShellMainThread Handler mainHandler
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
@@ -1506,6 +1489,7 @@ public abstract class WMShellModule {
shellTaskOrganizer,
desktopWallpaperActivityTokenProvider,
inputManager,
+ displayController,
mainHandler));
}
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
index 904d86282c39..6dcc0deb1da1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
@@ -35,9 +35,11 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
/** Controls the display windowing mode in desktop mode */
@@ -49,6 +51,7 @@ class DesktopDisplayModeController(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
private val inputManager: InputManager,
+ private val displayController: DisplayController,
@ShellMainThread private val mainHandler: Handler,
) {
@@ -128,14 +131,25 @@ class DesktopDisplayModeController(
return windowManager.getWindowingMode(DEFAULT_DISPLAY)
}
- // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
- private fun isExtendedDisplayEnabled() =
- 0 !=
+ private fun isExtendedDisplayEnabled(): Boolean {
+ if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue) {
+ return rootTaskDisplayAreaOrganizer
+ .getDisplayIds()
+ .filter { it != DEFAULT_DISPLAY }
+ .any { displayId ->
+ displayController.getDisplay(displayId)?.let { display ->
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)
+ } ?: false
+ }
+ }
+
+ return 0 !=
Settings.Global.getInt(
context.contentResolver,
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
0,
)
+ }
private fun hasExternalDisplay() =
rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt
deleted file mode 100644
index efd3866e1bc4..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.desktopmode
-
-import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
-import android.os.IBinder
-import android.window.DesktopModeFlags
-import android.window.TransitionInfo
-import com.android.internal.protolog.ProtoLog
-import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-
-/**
- * Observer of PiP in Desktop Mode transitions. At the moment, this is specifically tracking a PiP
- * transition for a task that is entering PiP via the minimize button on the caption bar.
- */
-class DesktopPipTransitionObserver {
- private val pendingPipTransitions = mutableMapOf<IBinder, PendingPipTransition>()
-
- /** Adds a pending PiP transition to be tracked. */
- fun addPendingPipTransition(transition: PendingPipTransition) {
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
- pendingPipTransitions[transition.token] = transition
- }
-
- /**
- * Called when any transition is ready, which may include transitions not tracked by this
- * observer.
- */
- fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
- val pipTransition = pendingPipTransitions.remove(transition) ?: return
-
- logD("Desktop PiP transition ready: %s", transition)
- for (change in info.changes) {
- val taskInfo = change.taskInfo
- if (taskInfo == null || taskInfo.taskId == -1) {
- continue
- }
-
- if (
- taskInfo.taskId == pipTransition.taskId &&
- taskInfo.windowingMode == WINDOWING_MODE_PINNED
- ) {
- logD("Desktop PiP transition was successful")
- pipTransition.onSuccess()
- return
- }
- }
- logD("Change with PiP task not found in Desktop PiP transition; likely failed")
- }
-
- /**
- * Data tracked for a pending PiP transition.
- *
- * @property token the PiP transition that is started.
- * @property taskId task id of the task entering PiP.
- * @property onSuccess callback to be invoked if the PiP transition is successful.
- */
- data class PendingPipTransition(val token: IBinder, val taskId: Int, val onSuccess: () -> Unit)
-
- private fun logD(msg: String, vararg arguments: Any?) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
- }
-
- private companion object {
- private const val TAG = "DesktopPipTransitionObserver"
- }
-}
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 0c2ee4648a43..a4e9c52ac9d9 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
@@ -68,6 +68,7 @@ class DesktopRepository(
* @property topTransparentFullscreenTaskId the task id of any current top transparent
* fullscreen task launched on top of the desk. Cleared when the transparent task is closed or
* sent to back. (top is at index 0).
+ * @property pipTaskId the task id of PiP task entered while in Desktop Mode.
*/
private data class Desk(
val deskId: Int,
@@ -80,6 +81,7 @@ class DesktopRepository(
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
var fullImmersiveTaskId: Int? = null,
var topTransparentFullscreenTaskId: Int? = null,
+ var pipTaskId: Int? = null,
) {
fun deepCopy(): Desk =
Desk(
@@ -92,6 +94,7 @@ class DesktopRepository(
freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
fullImmersiveTaskId = fullImmersiveTaskId,
topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
+ pipTaskId = pipTaskId,
)
// TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
@@ -104,6 +107,7 @@ class DesktopRepository(
freeformTasksInZOrder.clear()
fullImmersiveTaskId = null
topTransparentFullscreenTaskId = null
+ pipTaskId = null
}
}
@@ -123,6 +127,9 @@ class DesktopRepository(
/* Tracks last bounds of task before toggled to immersive state. */
private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()
+ /* Callback for when a pending PiP transition has been aborted. */
+ private var onPipAbortedCallback: ((Int, Int) -> Unit)? = null
+
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
@@ -332,7 +339,7 @@ class DesktopRepository(
val affectedDisplays = mutableSetOf<Int>()
desktopData
.desksSequence()
- .filter { desk -> desk.displayId != excludedDeskId }
+ .filter { desk -> desk.deskId != excludedDeskId }
.forEach { desk ->
val removed = removeActiveTaskFromDesk(desk.deskId, taskId, notifyListeners = false)
if (removed) {
@@ -604,6 +611,57 @@ class DesktopRepository(
}
/**
+ * Set whether the given task is the Desktop-entered PiP task in this display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
+ val activeDesk =
+ desktopData.getActiveDesk(displayId)
+ ?: error("Expected active desk in display: $displayId")
+ if (enterPip) {
+ activeDesk.pipTaskId = taskId
+ } else {
+ activeDesk.pipTaskId =
+ if (activeDesk.pipTaskId == taskId) null
+ else {
+ logW(
+ "setTaskInPip: taskId=%d did not match saved taskId=%d",
+ taskId,
+ activeDesk.pipTaskId,
+ )
+ activeDesk.pipTaskId
+ }
+ }
+ }
+
+ /**
+ * Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
+ desktopData.getActiveDesk(displayId)?.pipTaskId == taskId
+
+ /**
+ * Saves callback to handle a pending PiP transition being aborted.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) {
+ onPipAbortedCallback = callbackIfPipAborted
+ }
+
+ /**
+ * Invokes callback to handle a pending PiP transition with the given task id being aborted.
+ *
+ * TODO: b/389960283 - add explicit [deskId] argument.
+ */
+ fun onPipAborted(displayId: Int, pipTaskId: Int) {
+ onPipAbortedCallback?.invoke(displayId, pipTaskId)
+ }
+
+ /**
* Set whether the given task is the full-immersive task in this display's active desk.
*
* TODO: b/389960283 - consider forcing callers to use [setTaskInFullImmersiveStateInDesk] with
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 1f0774c24143..5e9cd9016d92 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
@@ -32,8 +32,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -45,6 +43,7 @@ import android.os.IBinder
import android.os.SystemProperties
import android.os.UserHandle
import android.util.Slog
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.MotionEvent
@@ -215,7 +214,6 @@ class DesktopTasksController(
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
private val desksOrganizer: DesksOrganizer,
private val desksTransitionObserver: DesksTransitionObserver,
- private val desktopPipTransitionObserver: DesktopPipTransitionObserver,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler,
@@ -466,6 +464,10 @@ class DesktopTasksController(
/** Creates a new desk in the given display. */
fun createDesk(displayId: Int) {
+ if (displayId == Display.INVALID_DISPLAY) {
+ logW("createDesk attempt with invalid displayId", displayId)
+ return
+ }
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksOrganizer.createDesk(displayId) { deskId ->
taskRepository.addDesk(displayId = displayId, deskId = deskId)
@@ -654,6 +656,7 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
taskSurface: SurfaceControl,
+ dragInterruptedCallback: Runnable,
) {
logV("startDragToDesktop taskId=%d", taskInfo.taskId)
val jankConfigBuilder =
@@ -669,6 +672,7 @@ class DesktopTasksController(
taskInfo,
dragToDesktopValueAnimator,
visualIndicator,
+ dragInterruptedCallback,
)
}
@@ -789,30 +793,10 @@ class DesktopTasksController(
fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val wct = WindowContainerTransaction()
- val taskId = taskInfo.taskId
- val displayId = taskInfo.displayId
- val deskId =
- taskRepository.getDeskIdForTask(taskInfo.taskId)
- ?: if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- logW("minimizeTask: desk not found for task: ${taskInfo.taskId}")
- return
- } else {
- getDefaultDeskId(taskInfo.displayId)
- }
- val isLastTask =
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- taskRepository.isOnlyVisibleNonClosingTaskInDesk(
- taskId = taskId,
- deskId = checkNotNull(deskId) { "Expected non-null deskId" },
- displayId = displayId,
- )
- } else {
- taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
- }
+
val isMinimizingToPip =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
- (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false)
-
+ (taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false)
// If task is going to PiP, start a PiP transition instead of a minimize transition
if (isMinimizingToPip) {
val requestInfo =
@@ -826,60 +810,75 @@ class DesktopTasksController(
)
val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
wct.merge(requestRes.second, true)
+ freeformTaskTransitionStarter.startPipTransition(wct)
+ taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
+ taskRepository.setOnPipAbortedCallback { displayId, taskId ->
+ minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason)
+ taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
+ }
+ return
+ }
- desktopPipTransitionObserver.addPendingPipTransition(
- DesktopPipTransitionObserver.PendingPipTransition(
- token = freeformTaskTransitionStarter.startPipTransition(wct),
- taskId = taskInfo.taskId,
- onSuccess = {
- onDesktopTaskEnteredPip(
- taskId = taskId,
- deskId = deskId,
- displayId = taskInfo.displayId,
- taskIsLastVisibleTaskBeforePip = isLastTask,
- )
- },
- )
+ minimizeTaskInner(taskInfo, minimizeReason)
+ }
+
+ private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
+ val taskId = taskInfo.taskId
+ val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
+ if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ logW("minimizeTaskInner: desk not found for task: ${taskInfo.taskId}")
+ return
+ }
+ val displayId = taskInfo.displayId
+ val wct = WindowContainerTransaction()
+
+ snapEventHandler.removeTaskIfTiled(displayId, taskId)
+ val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = willExitDesktop,
+ )
+ // Notify immersive handler as it might need to exit immersive state.
+ val exitResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ taskInfo = taskInfo,
+ reason = DesktopImmersiveController.ExitReason.MINIMIZED,
+ )
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ desksOrganizer.minimizeTask(
+ wct = wct,
+ deskId = checkNotNull(deskId) { "Expected non-null deskId" },
+ task = taskInfo,
)
} else {
- snapEventHandler.removeTaskIfTiled(displayId, taskId)
- val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
- val desktopExitRunnable =
- performDesktopExitCleanUp(
- wct = wct,
- deskId = deskId,
- displayId = displayId,
- willExitDesktop = willExitDesktop,
- )
- // Notify immersive handler as it might need to exit immersive state.
- val exitResult =
- desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- taskInfo = taskInfo,
- reason = DesktopImmersiveController.ExitReason.MINIMIZED,
- )
+ wct.reorder(taskInfo.token, /* onTop= */ false)
+ }
+ val isLastTask =
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksOrganizer.minimizeTask(
- wct = wct,
+ taskRepository.isOnlyVisibleNonClosingTaskInDesk(
+ taskId = taskId,
deskId = checkNotNull(deskId) { "Expected non-null deskId" },
- task = taskInfo,
- )
- } else {
- wct.reorder(taskInfo.token, /* onTop= */ false)
- }
- val transition =
- freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
- desktopTasksLimiter.ifPresent {
- it.addPendingMinimizeChange(
- transition = transition,
displayId = displayId,
- taskId = taskId,
- minimizeReason = minimizeReason,
)
+ } else {
+ taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
}
- exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- desktopExitRunnable?.invoke(transition)
+ val transition =
+ freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
+ desktopTasksLimiter.ifPresent {
+ it.addPendingMinimizeChange(
+ transition = transition,
+ displayId = displayId,
+ taskId = taskId,
+ minimizeReason = minimizeReason,
+ )
}
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+ desktopExitRunnable?.invoke(transition)
}
/** Move a task with given `taskId` to fullscreen */
@@ -1262,8 +1261,7 @@ class DesktopTasksController(
wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true)
}
- // TODO: b/391485148 - pass in the moving-to-desk |task| here to apply task-limit policy.
- val activationRunnable = addDeskActivationChanges(destinationDeskId, wct)
+ val activationRunnable = addDeskActivationChanges(destinationDeskId, wct, task)
if (Flags.enableDisplayFocusInShellTransitions()) {
// Bring the destination display to top with includingParents=true, so that the
@@ -1847,11 +1845,7 @@ class DesktopTasksController(
displayId: Int,
forceExitDesktop: Boolean,
): Boolean {
- if (
- forceExitDesktop &&
- (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ||
- DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue)
- ) {
+ if (forceExitDesktop && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
// |forceExitDesktop| is true when the callers knows we'll exit desktop, such as when
// explicitly going fullscreen, so there's no point in checking the desktop state.
return true
@@ -1868,33 +1862,6 @@ class DesktopTasksController(
return true
}
- /** Potentially perform Desktop cleanup after a task successfully enters PiP. */
- @VisibleForTesting
- fun onDesktopTaskEnteredPip(
- taskId: Int,
- deskId: Int,
- displayId: Int,
- taskIsLastVisibleTaskBeforePip: Boolean,
- ) {
- if (
- !willExitDesktop(taskId, displayId, forceExitDesktop = taskIsLastVisibleTaskBeforePip)
- ) {
- return
- }
-
- val wct = WindowContainerTransaction()
- val desktopExitRunnable =
- performDesktopExitCleanUp(
- wct = wct,
- deskId = deskId,
- displayId = displayId,
- willExitDesktop = true,
- )
-
- val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
- desktopExitRunnable?.invoke(transition)
- }
-
private fun performDesktopExitCleanupIfNeeded(
taskId: Int,
deskId: Int? = null,
@@ -2981,6 +2948,11 @@ class DesktopTasksController(
removeDesk(displayId = displayId, deskId = deskId)
}
+ /** Removes all the available desks on all displays. */
+ fun removeAllDesks() {
+ taskRepository.getAllDeskIds().forEach { deskId -> removeDesk(deskId) }
+ }
+
private fun removeDesk(displayId: Int, deskId: Int) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
logV("removeDesk deskId=%d from displayId=%d", deskId, displayId)
@@ -3747,6 +3719,18 @@ class DesktopTasksController(
}
}
+ override fun removeDesk(deskId: Int) {
+ executeRemoteCallWithTaskPermission(controller, "removeDesk") { c ->
+ c.removeDesk(deskId)
+ }
+ }
+
+ override fun removeAllDesks() {
+ executeRemoteCallWithTaskPermission(controller, "removeAllDesks") { c ->
+ c.removeAllDesks()
+ }
+ }
+
override fun activateDesk(deskId: Int, remoteTransition: RemoteTransition?) {
executeRemoteCallWithTaskPermission(controller, "activateDesk") { c ->
c.activateDesk(deskId, remoteTransition)
@@ -3810,8 +3794,8 @@ class DesktopTasksController(
}
}
- override fun removeDesktop(displayId: Int) {
- executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c ->
+ override fun removeDefaultDeskInDisplay(displayId: Int) {
+ executeRemoteCallWithTaskPermission(controller, "removeDefaultDeskInDisplay") { c ->
c.removeDefaultDeskInDisplay(displayId)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index c670ac3c4488..2ec6105e5af9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -23,6 +23,7 @@ import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
@@ -37,9 +38,13 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktop
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.TransitionUtil.isClosingMode
+import com.android.wm.shell.shared.TransitionUtil.isOpeningMode
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
+import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
@@ -52,7 +57,6 @@ class DesktopTasksTransitionObserver(
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
- private val desktopPipTransitionObserver: DesktopPipTransitionObserver,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
shellInit: ShellInit,
@@ -61,6 +65,8 @@ class DesktopTasksTransitionObserver(
data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int)
private var transitionToCloseWallpaper: CloseWallpaperTransition? = null
+ /* Pending PiP transition and its associated display id and task id. */
+ private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null
private var currentProfileId: Int
init {
@@ -94,7 +100,33 @@ class DesktopTasksTransitionObserver(
removeTaskIfNeeded(info)
}
removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
- desktopPipTransitionObserver.onTransitionReady(transition, info)
+
+ val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
+ info.changes.forEach { change ->
+ change.taskInfo?.let { taskInfo ->
+ if (
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
+ desktopRepository.isTaskMinimizedPipInDisplay(
+ taskInfo.displayId,
+ taskInfo.taskId,
+ )
+ ) {
+ when (info.type) {
+ TRANSIT_PIP ->
+ pendingPipTransitionAndPipTask =
+ Triple(transition, taskInfo.displayId, taskInfo.taskId)
+
+ TRANSIT_EXIT_PIP,
+ TRANSIT_REMOVE_PIP ->
+ desktopRepository.setTaskInPip(
+ taskInfo.displayId,
+ taskInfo.taskId,
+ enterPip = false,
+ )
+ }
+ }
+ }
+ }
}
private fun removeTaskIfNeeded(info: TransitionInfo) {
@@ -269,6 +301,18 @@ class DesktopTasksTransitionObserver(
}
}
transitionToCloseWallpaper = null
+ } else if (pendingPipTransitionAndPipTask?.first == transition) {
+ val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
+ if (aborted) {
+ pendingPipTransitionAndPipTask?.let {
+ desktopRepository.onPipAborted(
+ /*displayId=*/ it.second,
+ /* taskId=*/ it.third,
+ )
+ }
+ }
+ desktopRepository.setOnPipAbortedCallback(null)
+ pendingPipTransitionAndPipTask = null
}
}
@@ -303,18 +347,29 @@ class DesktopTasksTransitionObserver(
}
private fun updateTopTransparentFullscreenTaskId(info: TransitionInfo) {
- info.changes.forEach { change ->
- change.taskInfo?.let { task ->
- val desktopRepository = desktopUserRepositories.getProfile(task.userId)
- val displayId = task.displayId
- // Clear `topTransparentFullscreenTask` information from repository if task
- // is closed or sent to back.
- if (
- TransitionUtil.isClosingMode(change.mode) &&
- task.taskId ==
- desktopRepository.getTopTransparentFullscreenTaskId(displayId)
- ) {
- desktopRepository.clearTopTransparentFullscreenTaskId(displayId)
+ run forEachLoop@{
+ info.changes.forEach { change ->
+ change.taskInfo?.let { task ->
+ val desktopRepository = desktopUserRepositories.getProfile(task.userId)
+ val displayId = task.displayId
+ val transparentTaskId =
+ desktopRepository.getTopTransparentFullscreenTaskId(displayId)
+ if (transparentTaskId == null) return@forEachLoop
+ val changeMode = change.mode
+ val taskId = task.taskId
+ val isTopTransparentFullscreenTaskClosing =
+ taskId == transparentTaskId && isClosingMode(changeMode)
+ val isNonTopTransparentFullscreenTaskOpening =
+ taskId != transparentTaskId && isOpeningMode(changeMode)
+ // Clear `topTransparentFullscreenTask` information from repository if task
+ // is closed, sent to back or if a different task is opened, brought to front.
+ if (
+ isTopTransparentFullscreenTaskClosing ||
+ isNonTopTransparentFullscreenTaskOpening
+ ) {
+ desktopRepository.clearTopTransparentFullscreenTaskId(displayId)
+ return@forEachLoop
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 24b2e4879546..c6f74728fd81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -2,6 +2,7 @@ package com.android.wm.shell.desktopmode
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
@@ -23,6 +24,7 @@ import android.os.IBinder
import android.os.SystemClock
import android.os.SystemProperties
import android.os.UserHandle
+import android.view.Choreographer
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CLOSE
@@ -48,6 +50,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.animation.Interpolators
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -122,6 +125,7 @@ sealed class DragToDesktopTransitionHandler(
taskInfo: RunningTaskInfo,
dragToDesktopAnimator: MoveToDesktopAnimator,
visualIndicator: DesktopModeVisualIndicator?,
+ dragCancelCallback: Runnable,
) {
if (inProgress) {
logV("Drag to desktop transition already in progress.")
@@ -168,6 +172,7 @@ sealed class DragToDesktopTransitionHandler(
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask,
visualIndicator = visualIndicator,
+ dragCancelCallback = dragCancelCallback,
)
} else {
TransitionState.FromFullscreen(
@@ -175,6 +180,7 @@ sealed class DragToDesktopTransitionHandler(
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
visualIndicator = visualIndicator,
+ dragCancelCallback = dragCancelCallback,
)
}
}
@@ -203,8 +209,9 @@ sealed class DragToDesktopTransitionHandler(
}
if (state.startInterrupted) {
logV("finishDragToDesktop: start was interrupted, returning")
- // We should only have interrupted the start transition after receiving a cancel/end
- // request, let that existing request play out and just return here.
+ // If start was interrupted we've either already requested a cancel/end transition - so
+ // we should let that request play out, or we're cancelling the drag-to-desktop
+ // transition altogether, so just return here.
return null
}
state.endTransitionToken =
@@ -221,6 +228,7 @@ sealed class DragToDesktopTransitionHandler(
*/
fun cancelDragToDesktopTransition(cancelState: CancelState) {
if (!inProgress) {
+ logV("cancelDragToDesktop: not in progress, returning")
// Don't attempt to cancel a drag to desktop transition since there is no transition in
// progress which means that the drag to desktop transition was never successfully
// started.
@@ -228,14 +236,17 @@ sealed class DragToDesktopTransitionHandler(
}
val state = requireTransitionState()
if (state.startAborted) {
+ logV("cancelDragToDesktop: start was aborted, clearing state")
// Don't attempt to cancel the drag-to-desktop since the start transition didn't
// succeed as expected. Just reset the state as if nothing happened.
clearState()
return
}
if (state.startInterrupted) {
- // We should only have interrupted the start transition after receiving a cancel/end
- // request, let that existing request play out and just return here.
+ logV("cancelDragToDesktop: start was interrupted, returning")
+ // If start was interrupted we've either already requested a cancel/end transition - so
+ // we should let that request play out, or we're cancelling the drag-to-desktop
+ // transition altogether, so just return here.
return
}
state.cancelState = cancelState
@@ -706,11 +717,7 @@ sealed class DragToDesktopTransitionHandler(
// end-transition, or if the end-transition is running on its own, then just wait until that
// finishes instead. If we've merged the cancel-transition we've finished the
// start-transition and won't reach this code.
- if (
- mergeTarget == state.startTransitionToken &&
- isCancelOrEndTransitionRequested(state) &&
- !state.mergedEndTransition
- ) {
+ if (mergeTarget == state.startTransitionToken && !state.mergedEndTransition) {
interruptStartTransition(state)
}
}
@@ -722,9 +729,23 @@ sealed class DragToDesktopTransitionHandler(
if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) {
return
}
- logV("interruptStartTransition")
- state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
- state.dragAnimator.cancelAnimator()
+ if (isCancelOrEndTransitionRequested(state)) {
+ logV("interruptStartTransition, bookend requested -> finish start transition")
+ // Finish the start-drag transition, we will finish the overall transition properly when
+ // receiving #startAnimation for Cancel/End.
+ state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
+ state.dragAnimator.cancelAnimator()
+ } else {
+ logV("interruptStartTransition, bookend not requested -> animate to Home")
+ // Animate to Home, and then finish the start-drag transition. Since there is no other
+ // (end/cancel) transition requested that will be the end of the overall transition.
+ state.dragAnimator.cancelAnimator()
+ state.dragCancelCallback?.run()
+ createInterruptToHomeAnimator(transactionSupplier.get(), state) {
+ state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
+ clearState()
+ }
+ }
state.activeCancelAnimation?.removeAllListeners()
state.activeCancelAnimation?.cancel()
state.activeCancelAnimation = null
@@ -738,6 +759,46 @@ sealed class DragToDesktopTransitionHandler(
.onActionCancel(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG)
}
+ private fun createInterruptToHomeAnimator(
+ transaction: Transaction,
+ state: TransitionState,
+ endCallback: Runnable,
+ ) {
+ val homeLeash = state.homeChange?.leash ?: error("Expected home leash to be non-null")
+ val draggedTaskLeash =
+ state.draggedTaskChange?.leash ?: error("Expected dragged leash to be non-null")
+ val homeAnimator = createInterruptAlphaAnimator(transaction, homeLeash, toShow = true)
+ val draggedTaskAnimator =
+ createInterruptAlphaAnimator(transaction, draggedTaskLeash, toShow = false)
+ val animatorSet = AnimatorSet()
+ animatorSet.playTogether(homeAnimator, draggedTaskAnimator)
+ animatorSet.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ endCallback.run()
+ }
+ }
+ )
+ animatorSet.start()
+ }
+
+ private fun createInterruptAlphaAnimator(
+ transaction: Transaction,
+ leash: SurfaceControl,
+ toShow: Boolean,
+ ) =
+ ValueAnimator.ofFloat(if (toShow) 0f else 1f, if (toShow) 1f else 0f).apply {
+ transaction.show(leash)
+ duration = DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animation ->
+ transaction
+ .setAlpha(leash, animation.animatedValue as Float)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
+ .apply()
+ }
+ }
+
protected open fun setupEndDragToDesktop(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
@@ -1060,6 +1121,7 @@ sealed class DragToDesktopTransitionHandler(
abstract var endTransitionToken: IBinder?
abstract var mergedEndTransition: Boolean
abstract var activeCancelAnimation: Animator?
+ abstract var dragCancelCallback: Runnable?
data class FromFullscreen(
override val draggedTaskId: Int,
@@ -1079,6 +1141,7 @@ sealed class DragToDesktopTransitionHandler(
override var endTransitionToken: IBinder? = null,
override var mergedEndTransition: Boolean = false,
override var activeCancelAnimation: Animator? = null,
+ override var dragCancelCallback: Runnable? = null,
var otherRootChanges: MutableList<Change> = mutableListOf(),
) : TransitionState()
@@ -1100,6 +1163,7 @@ sealed class DragToDesktopTransitionHandler(
override var endTransitionToken: IBinder? = null,
override var mergedEndTransition: Boolean = false,
override var activeCancelAnimation: Animator? = null,
+ override var dragCancelCallback: Runnable? = null,
var splitRootChange: Change? = null,
var otherSplitTask: Int,
) : TransitionState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 44f7e16e98c3..5f7fbd9843d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -35,6 +35,12 @@ interface IDesktopMode {
/** Activates the desk whose ID is `deskId` on whatever display it currently exists on. */
oneway void activateDesk(int deskId, in RemoteTransition remoteTransition);
+ /** Removes the desk with the given `deskId`. */
+ oneway void removeDesk(int deskId);
+
+ /** Removes all the available desks on all displays. */
+ oneway void removeAllDesks();
+
/** Show apps on the desktop on the given display */
void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
@@ -64,8 +70,11 @@ interface IDesktopMode {
in @nullable RemoteTransition remoteTransition,
in @nullable IMoveToDesktopCallback callback);
- /** Remove desktop on the given display */
- oneway void removeDesktop(int displayId);
+ /**
+ * Removes the default desktop on the given display.
+ * @deprecated with multi-desks, we should use `removeDesk()`.
+ */
+ oneway void removeDefaultDeskInDisplay(int displayId);
/** Move a task with given `taskId` to external display */
void moveToExternalDisplay(int taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt
new file mode 100644
index 000000000000..8ce624e103ef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.common
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Handler
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.sysui.ShellInit
+import java.util.function.Supplier
+
+/**
+ * This supplies the package name of default home in an efficient way. The query to package manager
+ * only executes on initialization and when the preferred activity (e.g. default home) is changed.
+ */
+class DefaultHomePackageSupplier(
+ private val context: Context,
+ shellInit: ShellInit,
+ @ShellMainThread private val mainHandler: Handler,
+) : BroadcastReceiver(), Supplier<String?> {
+
+ private var defaultHomePackage: String? = null
+
+ init {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+
+ private fun onInit() {
+ context.registerReceiver(
+ this,
+ IntentFilter(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED),
+ null /* broadcastPermission */,
+ mainHandler,
+ )
+ }
+
+ private fun updateDefaultHomePackage(): String? {
+ defaultHomePackage = context.packageManager.getHomeActivities(ArrayList())?.packageName
+ return defaultHomePackage
+ }
+
+ override fun onReceive(contxt: Context?, intent: Intent?) {
+ updateDefaultHomePackage()
+ }
+
+ override fun get(): String? {
+ return defaultHomePackage ?: updateDefaultHomePackage()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index f89ba0a168d1..0bf2ea61b0a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -174,7 +174,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
SurfaceControl.Transaction finishT) {
mTaskChangeListener.ifPresent(listener -> listener.onTaskChanging(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
}
private void onToFrontTransitionReady(
@@ -184,7 +184,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
mTaskChangeListener.ifPresent(
listener -> listener.onTaskMovingToFront(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
}
private void onToBackTransitionReady(
@@ -194,7 +194,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
mTaskChangeListener.ifPresent(
listener -> listener.onTaskMovingToBack(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index f81f330e50c4..a02a51f92b1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -180,10 +180,17 @@ public class PipScheduler implements PipTransitionState.PipTransitionStateChange
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(pipTaskToken, toBounds);
if (configAtEnd) {
wct.deferConfigToTransitionEnd(pipTaskToken);
+
+ if (mPipBoundsState.getBounds().width() == toBounds.width()
+ && mPipBoundsState.getBounds().height() == toBounds.height()) {
+ // TODO (b/393159816): Config-at-End causes a flicker without size change.
+ // If PiP size isn't changing enforce a minimal one-pixel change as a workaround.
+ --toBounds.bottom;
+ }
}
+ wct.setBounds(pipTaskToken, toBounds);
mPipTransitionController.startResizeTransition(wct, duration);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 6fdfecaf15d5..d1bc450c3c4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -934,6 +934,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
}
// the size to toggle to after a double tap
+ mPipBoundsState.setNormalBounds(getAdjustedNormalBounds());
int nextSize = PipDoubleTapHelper
.nextSizeSpec(mPipBoundsState, getUserResizeBounds());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 75c09829e551..a7cba76ea91f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -123,6 +123,7 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.DesktopExperienceFlags;
+import android.window.DesktopModeFlags;
import android.window.DisplayAreaInfo;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -675,7 +676,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!enteredSplitSelect) {
return null;
}
- if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()
+ && !DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
mTaskOrganizer.applyTransaction(wct);
return null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 938885cc1684..23dfb41d52c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
@@ -50,6 +51,7 @@ public class HomeTransitionObserver implements TransitionObserver,
private @NonNull final Context mContext;
private @NonNull final ShellExecutor mMainExecutor;
+ private IBinder mPendingStartDragTransition;
private Boolean mPendingHomeVisibilityUpdate;
public HomeTransitionObserver(@NonNull Context context,
@@ -63,31 +65,42 @@ public class HomeTransitionObserver implements TransitionObserver,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
- if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
- handleTransitionReadyWithBubbleAnything(info);
- } else {
- handleTransitionReady(info);
+ Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info);
+
+ if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) {
+ // Do not apply at the start of desktop drag as that updates launcher UI visibility.
+ // Store the value and apply with a next transition or when cancelling the
+ // desktop-drag transition.
+ storePendingHomeVisibilityUpdate(transition, homeVisibilityUpdate);
+ return;
+ }
+
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && info.getType() == TRANSIT_CONVERT_TO_BUBBLE
+ && homeVisibilityUpdate == null) {
+ // We are converting to bubble and we did not get a change to home visibility in this
+ // transition. Apply the value from start of drag.
+ homeVisibilityUpdate = mPendingHomeVisibilityUpdate;
+ }
+
+ if (homeVisibilityUpdate != null) {
+ mPendingHomeVisibilityUpdate = null;
+ mPendingStartDragTransition = null;
+ notifyHomeVisibilityChanged(homeVisibilityUpdate);
}
}
- private void handleTransitionReady(@NonNull TransitionInfo info) {
- for (TransitionInfo.Change change : info.getChanges()) {
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null
- || info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
- || taskInfo.displayId != DEFAULT_DISPLAY
- || taskInfo.taskId == -1
- || !taskInfo.isRunning) {
- continue;
- }
- Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info, change, taskInfo);
- if (homeVisibilityUpdate != null) {
- notifyHomeVisibilityChanged(homeVisibilityUpdate);
- }
+ private void storePendingHomeVisibilityUpdate(
+ IBinder transition, Boolean homeVisibilityUpdate) {
+ if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && !ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) {
+ return;
}
+ mPendingHomeVisibilityUpdate = homeVisibilityUpdate;
+ mPendingStartDragTransition = transition;
}
- private void handleTransitionReadyWithBubbleAnything(@NonNull TransitionInfo info) {
+ private Boolean getHomeVisibilityUpdate(TransitionInfo info) {
Boolean homeVisibilityUpdate = null;
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -97,29 +110,12 @@ public class HomeTransitionObserver implements TransitionObserver,
|| !taskInfo.isRunning) {
continue;
}
-
Boolean update = getHomeVisibilityUpdate(info, change, taskInfo);
if (update != null) {
homeVisibilityUpdate = update;
}
}
-
- if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) {
- // Do not apply at the start of desktop drag as that updates launcher UI visibility.
- // Store the value and apply with a next transition if needed.
- mPendingHomeVisibilityUpdate = homeVisibilityUpdate;
- return;
- }
-
- if (info.getType() == TRANSIT_CONVERT_TO_BUBBLE && homeVisibilityUpdate == null) {
- // We are converting to bubble and we did not get a change to home visibility in this
- // transition. Apply the value from start of drag.
- homeVisibilityUpdate = mPendingHomeVisibilityUpdate;
- }
- if (homeVisibilityUpdate != null) {
- mPendingHomeVisibilityUpdate = null;
- notifyHomeVisibilityChanged(homeVisibilityUpdate);
- }
+ return homeVisibilityUpdate;
}
private Boolean getHomeVisibilityUpdate(TransitionInfo info,
@@ -146,7 +142,24 @@ public class HomeTransitionObserver implements TransitionObserver,
@Override
public void onTransitionFinished(@NonNull IBinder transition,
- boolean aborted) {}
+ boolean aborted) {
+ if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) {
+ return;
+ }
+ // Handle the case where the DragToDesktop START transition is interrupted and we never
+ // receive a CANCEL/END transition.
+ if (mPendingStartDragTransition == null
+ || mPendingStartDragTransition != transition) {
+ return;
+ }
+ mPendingStartDragTransition = null;
+ if (aborted) return;
+
+ if (mPendingHomeVisibilityUpdate != null) {
+ notifyHomeVisibilityChanged(mPendingHomeVisibilityUpdate);
+ mPendingHomeVisibilityUpdate = null;
+ }
+ }
/**
* Sets the home transition listener that receives any transitions resulting in a change of
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
deleted file mode 100644
index 978b8da2eb6d..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.transition;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
-
-import android.annotation.NonNull;
-import android.os.RemoteException;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.IWindowContainerTransactionCallback;
-
-import com.android.internal.protolog.ProtoLog;
-
-/**
- * Utilities and interfaces for transition-like usage on top of the legacy app-transition and
- * synctransaction tools.
- */
-public class LegacyTransitions {
-
- /**
- * Interface for a "legacy" transition. Effectively wraps a sync callback + remoteAnimation
- * into one callback.
- */
- public interface ILegacyTransition {
- /**
- * Called when both the associated sync transaction finishes and the remote animation is
- * ready.
- */
- void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t);
- }
-
- /**
- * Makes sure that a remote animation and corresponding sync callback are called together
- * such that the sync callback is called first. This assumes that both the callback receiver
- * and the remoteanimation are in the same process so that order is preserved on both ends.
- */
- public static class LegacyTransition {
- private final ILegacyTransition mLegacyTransition;
- private int mSyncId = -1;
- private SurfaceControl.Transaction mTransaction;
- private int mTransit;
- private RemoteAnimationTarget[] mApps;
- private RemoteAnimationTarget[] mWallpapers;
- private RemoteAnimationTarget[] mNonApps;
- private IRemoteAnimationFinishedCallback mFinishCallback = null;
- private boolean mCancelled = false;
- private final SyncCallback mSyncCallback = new SyncCallback();
- private final RemoteAnimationAdapter mAdapter =
- new RemoteAnimationAdapter(new RemoteAnimationWrapper(), 0, 0);
-
- public LegacyTransition(@WindowManager.TransitionType int type,
- @NonNull ILegacyTransition legacyTransition) {
- mLegacyTransition = legacyTransition;
- mTransit = type;
- }
-
- public @WindowManager.TransitionType int getType() {
- return mTransit;
- }
-
- public IWindowContainerTransactionCallback getSyncCallback() {
- return mSyncCallback;
- }
-
- public RemoteAnimationAdapter getAdapter() {
- return mAdapter;
- }
-
- private class SyncCallback extends IWindowContainerTransactionCallback.Stub {
- @Override
- public void onTransactionReady(int id, SurfaceControl.Transaction t)
- throws RemoteException {
- ProtoLog.v(WM_SHELL_TRANSITIONS,
- "LegacyTransitions.onTransactionReady(): syncId=%d", id);
- mSyncId = id;
- mTransaction = t;
- checkApply(true /* log */);
- }
- }
-
- private class RemoteAnimationWrapper extends IRemoteAnimationRunner.Stub {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- mTransit = transit;
- mApps = apps;
- mWallpapers = wallpapers;
- mNonApps = nonApps;
- mFinishCallback = finishedCallback;
- checkApply(false /* log */);
- }
-
- @Override
- public void onAnimationCancelled() throws RemoteException {
- mCancelled = true;
- mApps = mWallpapers = mNonApps = null;
- checkApply(false /* log */);
- }
- }
-
-
- private void checkApply(boolean log) throws RemoteException {
- if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) {
- if (log) {
- ProtoLog.v(WM_SHELL_TRANSITIONS, "\tSkipping hasFinishedCb=%b canceled=%b",
- mFinishCallback != null, mCancelled);
- }
- return;
- }
- if (log) {
- ProtoLog.v(WM_SHELL_TRANSITIONS, "\tapply");
- }
- mLegacyTransition.onAnimationStart(mTransit, mApps, mWallpapers,
- mNonApps, mFinishCallback, mTransaction);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 7871179a50de..42321e56e72b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -49,6 +49,7 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -233,7 +234,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction finishT,
+ @TransitionInfo.TransitionMode int changeMode) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 2d7fec3a5302..5e8c1fe2aa8d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -445,4 +445,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
return R.dimen.freeform_decor_caption_height;
}
+
+ @Override
+ int getCaptionViewId() {
+ return R.id.caption;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
index 2b2cdf84005c..4511fbe10764 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
@@ -31,6 +31,7 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.view.View;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -159,7 +160,8 @@ public abstract class CarWindowDecorViewModel
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction finishT,
+ @TransitionInfo.TransitionMode int changeMode) {
final CarWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
index 88f64bca280d..3182745d813e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
@@ -108,6 +108,11 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou
return new Rect();
}
+ @Override
+ int getCaptionViewId() {
+ return R.id.caption;
+ }
+
private void updateRelayoutParams(
RelayoutParams relayoutParams,
ActivityManager.RunningTaskInfo taskInfo,
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 a1d2774ee428..f2ff39627362 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
@@ -27,6 +27,7 @@ import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
@@ -79,6 +80,7 @@ import android.view.ViewConfiguration;
import android.view.ViewRootImpl;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -150,18 +152,19 @@ import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
import com.android.wm.shell.windowdecor.tiling.SnapEventHandler;
+import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Pair;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
-import org.jetbrains.annotations.NotNull;
-
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
import kotlinx.coroutines.MainCoroutineDispatcher;
+import org.jetbrains.annotations.NotNull;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -206,6 +209,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final AppToWebEducationController mAppToWebEducationController;
private final AppHandleAndHeaderVisibilityHelper mAppHandleAndHeaderVisibilityHelper;
private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
+ private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -331,6 +335,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
new AppHeaderViewHolder.Factory(),
+ new AppHandleViewHolder.Factory(),
rootTaskDisplayAreaOrganizer,
new SparseArray<>(),
interactionJankMonitor,
@@ -380,6 +385,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
+ AppHandleViewHolder.Factory appHandleViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
InteractionJankMonitor interactionJankMonitor,
@@ -422,6 +428,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
+ mAppHandleViewHolderFactory = appHandleViewHolderFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mGenericLinksParser = genericLinksParser;
mInputManager = mContext.getSystemService(InputManager.class);
@@ -486,7 +493,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
new DesktopModeOnTaskResizeAnimationListener());
mDesktopTasksController.setOnTaskRepositionAnimationListener(
new DesktopModeOnTaskRepositionAnimationListener());
- if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()
+ || DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
mRecentsTransitionHandler.addTransitionStateListener(
new DesktopModeRecentsTransitionStateListener());
}
@@ -587,7 +595,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction finishT,
+ @TransitionInfo.TransitionMode int changeMode) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
if (decoration != null) {
@@ -601,8 +610,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo),
- mExclusionRegion);
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion,
+ /*isMovingToBack= */ changeMode == TRANSIT_TO_BACK);
}
}
@@ -617,7 +626,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo),
- mExclusionRegion);
+ mExclusionRegion, /* isMovingToBack= */ false);
}
@Override
@@ -817,9 +826,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return;
}
decoration.closeHandleMenu();
- // When the app enters split-select, the handle will no longer be visible, meaning
- // we shouldn't receive input for it any longer.
- decoration.disposeStatusBarInputLayer();
mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_SPLIT_SCREEN);
@@ -979,6 +985,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private boolean mIsCustomHeaderGesture;
private boolean mIsResizeGesture;
private boolean mIsDragging;
+ private boolean mDragInterrupted;
private boolean mLongClickDisabled;
private int mDragPointerId = -1;
private MotionEvent mMotionEvent;
@@ -1216,7 +1223,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
View v, MotionEvent e) {
final int id = v.getId();
if (id == R.id.caption_handle) {
- handleCaptionThroughStatusBar(e, decoration);
+ handleCaptionThroughStatusBar(e, decoration,
+ /* interruptDragCallback= */
+ () -> {
+ mDragInterrupted = true;
+ setIsDragging(decoration, /* isDragging= */ false);
+ });
final boolean wasDragging = mIsDragging;
updateDragStatus(decoration, e);
final boolean upOrCancel = e.getActionMasked() == ACTION_UP
@@ -1333,11 +1345,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ mDragInterrupted = false;
setIsDragging(decor, false /* isDragging */);
break;
}
case MotionEvent.ACTION_MOVE: {
- setIsDragging(decor, true /* isDragging */);
+ if (!mDragInterrupted) {
+ setIsDragging(decor, true /* isDragging */);
+ }
break;
}
}
@@ -1458,7 +1473,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (!mInImmersiveMode && (relevantDecor == null
|| relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
|| mTransitionDragActive)) {
- handleCaptionThroughStatusBar(ev, relevantDecor);
+ handleCaptionThroughStatusBar(ev, relevantDecor,
+ /* interruptDragCallback= */ () -> {});
}
}
handleEventOutsideCaption(ev, relevantDecor);
@@ -1498,7 +1514,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
* Turn on desktop mode if handle is dragged below status bar.
*/
private void handleCaptionThroughStatusBar(MotionEvent ev,
- DesktopModeWindowDecoration relevantDecor) {
+ DesktopModeWindowDecoration relevantDecor, Runnable interruptDragCallback) {
if (relevantDecor == null) {
if (ev.getActionMasked() == ACTION_UP) {
mMoveToDesktopAnimator = null;
@@ -1599,7 +1615,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator, relevantDecor.mTaskSurface);
+ mMoveToDesktopAnimator, relevantDecor.mTaskSurface,
+ /* dragInterruptedCallback= */ () -> {
+ // Don't call into DesktopTasksController to cancel the
+ // transition here - the transition handler already handles
+ // that (including removing the visual indicator).
+ mTransitionDragActive = false;
+ mMoveToDesktopAnimator = null;
+ relevantDecor.handleDragInterrupted();
+ interruptDragCallback.run();
+ });
}
}
if (mMoveToDesktopAnimator != null) {
@@ -1761,6 +1786,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mMainChoreographer,
mSyncQueue,
mAppHeaderViewHolderFactory,
+ mAppHandleViewHolderFactory,
mRootTaskDisplayAreaOrganizer,
mGenericLinksParser,
mAssistContentRequester,
@@ -1852,12 +1878,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo),
- mExclusionRegion);
+ mExclusionRegion, /* isMovingToBack= */ false);
if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
incrementEventReceiverTasks(taskInfo.displayId);
}
}
+ @Nullable
private RunningTaskInfo getOtherSplitTask(int taskId) {
@SplitPosition int remainingTaskPosition = mSplitScreenController
.getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
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 1b5fdbb973d5..003baae29114 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
@@ -190,6 +190,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private ExclusionRegionListener mExclusionRegionListener;
private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
+ private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final MaximizeMenuFactory mMaximizeMenuFactory;
private final HandleMenuFactory mHandleMenuFactory;
@@ -212,6 +213,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private boolean mIsDragging = false;
private Runnable mLoadAppInfoRunnable;
private Runnable mSetAppInfoRunnable;
+ private boolean mIsMovingToBack;
public DesktopModeWindowDecoration(
Context context,
@@ -231,6 +233,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Choreographer choreographer,
SyncTransactionQueue syncQueue,
AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
+ AppHandleViewHolder.Factory appHandleViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
@@ -242,10 +245,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
this (context, userContext, displayController, taskResourceLoader, splitScreenController,
desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler,
mainExecutor, mainDispatcher, bgScope, bgExecutor, choreographer, syncQueue,
- appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser,
- assistContentRequester, SurfaceControl.Builder::new,
- SurfaceControl.Transaction::new, WindowContainerTransaction::new,
- SurfaceControl::new, new WindowManagerWrapper(
+ appHeaderViewHolderFactory, appHandleViewHolderFactory,
+ rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
+ SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
+ WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
context.getSystemService(WindowManager.class)),
new SurfaceControlViewHostFactory() {},
windowDecorViewHostSupplier,
@@ -273,6 +276,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Choreographer choreographer,
SyncTransactionQueue syncQueue,
AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
+ AppHandleViewHolder.Factory appHandleViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
@@ -302,6 +306,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
+ mAppHandleViewHolderFactory = appHandleViewHolderFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mGenericLinksParser = genericLinksParser;
mAssistContentRequester = assistContentRequester;
@@ -462,7 +467,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// causes flickering. See b/270202228.
final boolean applyTransactionOnDraw = taskInfo.isFreeform();
relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
- hasGlobalFocus, displayExclusionRegion);
+ hasGlobalFocus, displayExclusionRegion, mIsMovingToBack);
if (!applyTransactionOnDraw) {
t.apply();
}
@@ -489,7 +494,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion,
+ boolean isMovingToBack) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()) {
@@ -512,12 +518,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId)
.isTaskInFullImmersiveState(taskInfo.taskId);
+ mIsMovingToBack = isMovingToBack;
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController,
applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
mIsDragging, mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
- displayExclusionRegion, mIsRecentsTransitionRunning,
- mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo));
+ displayExclusionRegion,
+ /* shouldIgnoreCornerRadius= */ mIsRecentsTransitionRunning
+ && DesktopModeFlags
+ .ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(),
+ mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo),
+ mIsRecentsTransitionRunning, mIsMovingToBack);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -558,29 +569,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
});
}
- final Point position = new Point();
- if (isAppHandle(mWindowDecorViewHolder)) {
- position.set(determineHandlePosition());
- }
if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
notifyCaptionStateChanged();
}
Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData");
if (isAppHandle(mWindowDecorViewHolder)) {
- mWindowDecorViewHolder.bindData(new AppHandleViewHolder.HandleData(
- mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight,
- isCaptionVisible()
- ));
+ updateAppHandleViewHolder();
} else {
- mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData(
- mTaskInfo,
- DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController),
- inFullImmersive,
- hasGlobalFocus,
- /* maximizeHoverEnabled= */ canOpenMaximizeMenu(
- /* animatingTaskResizeOrReposition= */ false)
- ));
+ updateAppHeaderViewHolder(inFullImmersive, hasGlobalFocus);
}
Trace.endSection();
@@ -857,9 +854,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
}
+ /** Update the view holder for app handle. */
+ private void updateAppHandleViewHolder() {
+ if (!isAppHandle(mWindowDecorViewHolder)) return;
+ asAppHandle(mWindowDecorViewHolder).bindData(new AppHandleViewHolder.HandleData(
+ mTaskInfo, determineHandlePosition(), mResult.mCaptionWidth,
+ mResult.mCaptionHeight, isCaptionVisible()
+ ));
+ }
+
+ /** Update the view holder for app header. */
+ private void updateAppHeaderViewHolder(boolean inFullImmersive, boolean hasGlobalFocus) {
+ if (!isAppHeader(mWindowDecorViewHolder)) return;
+ asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData(
+ mTaskInfo,
+ DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController),
+ inFullImmersive,
+ hasGlobalFocus,
+ /* maximizeHoverEnabled= */ canOpenMaximizeMenu(
+ /* animatingTaskResizeOrReposition= */ false)
+ ));
+ }
+
private WindowDecorationViewHolder createViewHolder() {
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) {
- return new AppHandleViewHolder(
+ return mAppHandleViewHolderFactory.create(
mResult.mRootView,
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
@@ -886,6 +905,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return viewHolder instanceof AppHandleViewHolder;
}
+ private boolean isAppHeader(WindowDecorationViewHolder viewHolder) {
+ return viewHolder instanceof AppHeaderViewHolder;
+ }
+
@Nullable
private AppHandleViewHolder asAppHandle(WindowDecorationViewHolder viewHolder) {
if (viewHolder instanceof AppHandleViewHolder) {
@@ -918,7 +941,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean hasGlobalFocus,
@NonNull Region displayExclusionRegion,
boolean shouldIgnoreCornerRadius,
- boolean shouldExcludeCaptionFromAppBounds) {
+ boolean shouldExcludeCaptionFromAppBounds,
+ boolean isRecentsTransitionRunning,
+ boolean isMovingToBack) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -935,11 +960,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// the first frame.
relayoutParams.mAsyncViewHost = isAppHandle;
- final boolean showCaption;
- if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) {
+ boolean showCaption;
+ // If this relayout is occurring from an observed TRANSIT_TO_BACK transition, do not
+ // show caption (this includes split select transition).
+ if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()
+ && isMovingToBack && !isDragging) {
+ showCaption = false;
+ } else if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) {
// If the task is being dragged, the caption should not be hidden so that it continues
// receiving input
showCaption = true;
+ } else if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()
+ && isRecentsTransitionRunning) {
+ // Caption should not be visible in recents.
+ showCaption = false;
} else if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
if (inFullImmersiveMode) {
showCaption = (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
@@ -1683,6 +1717,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ /**
+ * Indicates that an app handle drag has been interrupted, this can happen e.g. if we receive an
+ * unknown transition during the drag-to-desktop transition.
+ */
+ void handleDragInterrupted() {
+ if (mResult.mRootView == null) return;
+ final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
+ handle.setHovered(false);
+ handle.setPressed(false);
+ }
+
private boolean pointInView(View v, float x, float y) {
return v != null && v.getLeft() <= x && v.getRight() >= x
&& v.getTop() <= y && v.getBottom() >= y;
@@ -1776,6 +1821,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode));
}
+ @Override
+ int getCaptionViewId() {
+ return R.id.desktop_mode_caption;
+ }
+
void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) {
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return;
final boolean inFullImmersive =
@@ -1795,9 +1845,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* <p> When a Recents transition is active we allow that transition to take ownership of the
* corner radius of its task surfaces, so each window decoration should stop updating the corner
* radius of its task surface during that time.
+ *
+ * We should not allow input to reach the input layer during a Recents transition, so
+ * update the handle view holder accordingly if transition status changes.
*/
void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) {
- mIsRecentsTransitionRunning = isRecentsTransitionRunning;
+ if (mIsRecentsTransitionRunning != isRecentsTransitionRunning) {
+ mIsRecentsTransitionRunning = isRecentsTransitionRunning;
+ if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
+ // We don't relayout decor on recents transition, so we need to call it directly.
+ relayout(mTaskInfo, mHasGlobalFocus, mRelayoutParams.mDisplayExclusionRegion);
+ }
+ }
}
/**
@@ -1862,6 +1921,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Choreographer choreographer,
SyncTransactionQueue syncQueue,
AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
+ AppHandleViewHolder.Factory appHandleViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
@@ -1889,6 +1949,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
choreographer,
syncQueue,
appHeaderViewHolderFactory,
+ appHandleViewHolderFactory,
rootTaskDisplayAreaOrganizer,
genericLinksParser,
assistContentRequester,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 2d6f7459e0ae..732f04259fd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -173,6 +173,9 @@ class FluidResizeTaskPositioner implements TaskPositioner, Transitions.Transitio
return new Rect(mRepositionTaskBounds);
}
+ @Override
+ public void close() {}
+
private boolean isResizing() {
return (mCtrlType & CTRL_TYPE_TOP) != 0 || (mCtrlType & CTRL_TYPE_BOTTOM) != 0
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index c544468f5191..a8a7032d0b86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -108,12 +108,9 @@ class HandleMenu(
private val isViewAboveStatusBar: Boolean
get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform)
- private val pillElevation: Int = loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_pill_elevation)
private val pillTopMargin: Int = loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
- private val menuWidth = loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_width) + pillElevation
+ private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_width)
private val menuHeight = getHandleMenuHeight()
private val marginMenuTop = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top)
private val marginMenuStart = loadDimensionPixelSize(
@@ -398,8 +395,7 @@ class HandleMenu(
* Determines handle menu height based the max size and the visibility of pills.
*/
private fun getHandleMenuHeight(): Int {
- var menuHeight = loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_height) + pillElevation
+ var menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_height)
if (!shouldShowWindowingPill) {
menuHeight -= loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_windowing_pill_height)
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 1b0e0f70ed21..eb324f74ca82 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
@@ -294,6 +294,10 @@ class MultiDisplayVeiledResizeTaskPositioner(
return Rect(repositionTaskBounds)
}
+ override fun close() {
+ displayController.removeDisplayWindowListener(this)
+ }
+
private fun resetVeilIfVisible() {
if (isResizingOrAnimatingResize) {
desktopWindowDecoration.hideResizeVeil()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
index 63b288d133dd..06e5380fa1de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
@@ -41,4 +41,10 @@ public interface TaskDragResizer {
*/
void removeDragEventListener(
DragPositioningCallbackUtility.DragEventListener dragEventListener);
+
+ /**
+ * Releases any resources associated with this TaskDragResizer. This should be called when the
+ * associated window is closed.
+ */
+ void close();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index d2c79d76e6c1..7e941ec0d31a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -205,6 +205,9 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
return new Rect(mRepositionTaskBounds);
}
+ @Override
+ public void close() {}
+
private boolean isResizing() {
return (mCtrlType & CTRL_TYPE_TOP) != 0 || (mCtrlType & CTRL_TYPE_BOTTOM) != 0
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 1563259f4a1a..5e4a0a5860f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
import android.view.SurfaceControl;
+import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -83,12 +84,14 @@ public interface WindowDecorViewModel {
* @param taskSurface the surface of the task
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
+ * @param changeMode the type of change to the task
*/
void onTaskChanging(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT);
+ SurfaceControl.Transaction finishT,
+ @TransitionInfo.TransitionMode int changeMode);
/**
* Notifies that the given task is about to close to give the window decoration a chance to
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 68e3d6e277e5..91a899c09407 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -629,16 +629,32 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
private void updateCaptionVisibility(View rootView, @NonNull RelayoutParams params) {
mIsCaptionVisible = params.mIsCaptionVisible;
+ setCaptionVisibility(rootView, mIsCaptionVisible);
}
void setTaskDragResizer(TaskDragResizer taskDragResizer) {
mTaskDragResizer = taskDragResizer;
}
+ // TODO(b/346441962): Move these three methods closer to implementing or View-level classes to
+ // keep implementation details more encapsulated.
+ private void setCaptionVisibility(View rootView, boolean visible) {
+ if (rootView == null) {
+ return;
+ }
+ final int v = visible ? View.VISIBLE : View.GONE;
+ final View captionView = rootView.findViewById(getCaptionViewId());
+ captionView.setVisibility(v);
+ }
+
int getCaptionHeightId(@WindowingMode int windowingMode) {
return Resources.ID_NULL;
}
+ int getCaptionViewId() {
+ return Resources.ID_NULL;
+ }
+
/**
* Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
* registers {@link #mOnDisplaysChangedListener} if it doesn't.
@@ -683,6 +699,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
public void close() {
Trace.beginSection("WindowDecoration#close");
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+ if (mTaskDragResizer != null) {
+ mTaskDragResizer.close();
+ }
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
releaseViews(wct);
mTaskOrganizer.applyTransaction(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleAnimator.kt
deleted file mode 100644
index f0a85306d177..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleAnimator.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.windowdecor
-
-import android.animation.ObjectAnimator
-import android.view.View
-import android.view.View.Visibility
-import android.view.animation.PathInterpolator
-import android.widget.ImageButton
-import androidx.core.animation.doOnEnd
-import com.android.wm.shell.shared.animation.Interpolators
-
-/**
- * Animates the Desktop View's app handle.
- */
-class AppHandleAnimator(
- private val appHandleView: View,
- private val captionHandle: ImageButton,
-) {
- companion object {
- // Constants for animating the whole caption
- private const val APP_HANDLE_ALPHA_FADE_IN_ANIMATION_DURATION_MS: Long = 275L
- private const val APP_HANDLE_ALPHA_FADE_OUT_ANIMATION_DURATION_MS: Long = 340
- private val APP_HANDLE_ANIMATION_INTERPOLATOR = PathInterpolator(
- 0.4f,
- 0f,
- 0.2f,
- 1f
- )
-
- // Constants for animating the caption's handle
- private const val HANDLE_ANIMATION_DURATION: Long = 100
- private val HANDLE_ANIMATION_INTERPOLATOR = Interpolators.FAST_OUT_SLOW_IN
- }
-
- private var animator: ObjectAnimator? = null
-
- /** Animates the given caption view to the given visibility after a visibility change. */
- fun animateVisibilityChange(@Visibility visible: Int) {
- when (visible) {
- View.VISIBLE -> animateShowAppHandle()
- else -> animateHideAppHandle()
- }
- }
-
- /** Animate appearance/disappearance of caption's handle. */
- fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) {
- cancel()
- animator = ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply {
- duration = HANDLE_ANIMATION_DURATION
- interpolator = HANDLE_ANIMATION_INTERPOLATOR
- start()
- }
- }
-
- private fun animateShowAppHandle() {
- cancel()
- appHandleView.alpha = 0f
- appHandleView.visibility = View.VISIBLE
- animator = ObjectAnimator.ofFloat(appHandleView, View.ALPHA, 1f).apply {
- duration = APP_HANDLE_ALPHA_FADE_IN_ANIMATION_DURATION_MS
- interpolator = APP_HANDLE_ANIMATION_INTERPOLATOR
- start()
- }
- }
-
- private fun animateHideAppHandle() {
- cancel()
- animator = ObjectAnimator.ofFloat(appHandleView, View.ALPHA, 0f).apply {
- duration = APP_HANDLE_ALPHA_FADE_OUT_ANIMATION_DURATION_MS
- interpolator = APP_HANDLE_ANIMATION_INTERPOLATOR
- doOnEnd {
- appHandleView.visibility = View.GONE
- }
- start()
- }
- }
-
- /**
- * Cancels any active animations.
- */
- fun cancel() {
- animator?.removeAllListeners()
- animator?.cancel()
- animator = null
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 7ab3303be618..0985587a330e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.windowdecor.viewholder
+import android.animation.ObjectAnimator
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.res.ColorStateList
@@ -39,8 +40,8 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.Accessibilit
import com.android.internal.policy.SystemBarUtils
import com.android.window.flags.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.shared.animation.Interpolators
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
-import com.android.wm.shell.windowdecor.AppHandleAnimator
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -48,7 +49,7 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystem
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
* It hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
*/
-internal class AppHandleViewHolder(
+class AppHandleViewHolder(
rootView: View,
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: OnClickListener,
@@ -56,19 +57,22 @@ internal class AppHandleViewHolder(
private val handler: Handler
) : WindowDecorationViewHolder<AppHandleViewHolder.HandleData>(rootView) {
+ companion object {
+ private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
+ }
+
data class HandleData(
val taskInfo: RunningTaskInfo,
val position: Point,
val width: Int,
val height: Int,
- val isCaptionVisible: Boolean
+ val showInputLayer: Boolean
) : Data()
private lateinit var taskInfo: RunningTaskInfo
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
private val inputManager = context.getSystemService(InputManager::class.java)
- private val animator: AppHandleAnimator = AppHandleAnimator(rootView, captionHandle)
private var statusBarInputLayerExists = false
// An invisible View that takes up the same coordinates as captionHandle but is layered
@@ -97,7 +101,7 @@ internal class AppHandleViewHolder(
}
override fun bindData(data: HandleData) {
- bindData(data.taskInfo, data.position, data.width, data.height, data.isCaptionVisible)
+ bindData(data.taskInfo, data.position, data.width, data.height, data.showInputLayer)
}
private fun bindData(
@@ -105,15 +109,13 @@ internal class AppHandleViewHolder(
position: Point,
width: Int,
height: Int,
- isCaptionVisible: Boolean
+ showInputLayer: Boolean
) {
- setVisibility(isCaptionVisible)
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
this.taskInfo = taskInfo
// If handle is not in status bar region(i.e., bottom stage in vertical split),
// do not create an input layer
- if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
- if (!isCaptionVisible) {
+ if (position.y >= SystemBarUtils.getStatusBarHeight(context) || !showInputLayer) {
disposeStatusBarInputLayer()
return
}
@@ -129,11 +131,11 @@ internal class AppHandleViewHolder(
}
override fun onHandleMenuOpened() {
- animator.animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
+ animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
}
override fun onHandleMenuClosed() {
- animator.animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
+ animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
}
private fun createStatusBarInputLayer(handlePosition: Point,
@@ -237,16 +239,6 @@ internal class AppHandleViewHolder(
}
}
- private fun setVisibility(visible: Boolean) {
- val v = if (visible) View.VISIBLE else View.GONE
- if (captionView.visibility == v) return
- if (!DesktopModeFlags.ENABLE_DESKTOP_APP_HANDLE_ANIMATION.isTrue()) {
- captionView.visibility = v
- return
- }
- animator.animateVisibilityChange(v)
- }
-
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_handle_bar_light)
@@ -272,7 +264,36 @@ internal class AppHandleViewHolder(
} ?: false
}
- override fun close() {
- animator.cancel()
+ /** Animate appearance/disappearance of caption handle as the handle menu is animated. */
+ private fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) {
+ val animator =
+ ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply {
+ duration = CAPTION_HANDLE_ANIMATION_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ animator.start()
+ }
+
+ override fun close() {}
+
+ /** Factory class for creating [AppHandleViewHolder] objects. */
+ class Factory {
+ /**
+ * Create a [AppHandleViewHolder] object to handle caption view and status bar
+ * input layer logic.
+ */
+ fun create(
+ rootView: View,
+ onCaptionTouchListener: View.OnTouchListener,
+ onCaptionButtonClickListener: OnClickListener,
+ windowManagerWrapper: WindowManagerWrapper,
+ handler: Handler,
+ ): AppHandleViewHolder = AppHandleViewHolder(
+ rootView,
+ onCaptionTouchListener,
+ onCaptionButtonClickListener,
+ windowManagerWrapper,
+ handler,
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
new file mode 100644
index 000000000000..68f7ef09ee70
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell
+
+import android.graphics.Point
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.DisplayListener
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * A TestRule to manage multiple simulated connected overlay displays.
+ */
+class SimulatedConnectedDisplayTestRule : TestRule {
+
+ private val context = InstrumentationRegistry.getInstrumentation().targetContext
+ private val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
+ private val displayManager = context.getSystemService(DisplayManager::class.java)
+ private val addedDisplays = mutableListOf<Int>()
+
+ override fun apply(base: Statement, description: Description): Statement =
+ object : Statement() {
+ override fun evaluate() {
+ try {
+ base.evaluate()
+ } finally {
+ teardown()
+ }
+ }
+ }
+
+ private fun teardown() {
+ cleanupTestDisplays()
+ }
+
+ /**
+ * Adds multiple overlay displays with specified dimensions. Any existing overlay displays
+ * will be removed before adding the new ones.
+ *
+ * @param displays A list of [Point] objects, where each [Point] represents the
+ * width and height of a simulated display.
+ * @return List of displayIds of added displays.
+ */
+ fun setupTestDisplays(displays: List<Point>): List<Int> = runBlocking {
+ // Cleanup any existing overlay displays.
+ cleanupTestDisplays()
+
+ if (displays.isEmpty()) {
+ Log.w(TAG, "setupTestDisplays called with an empty list. No displays created.")
+ return@runBlocking emptyList()
+ }
+
+ val displayAddedFlow: Flow<Int> = callbackFlow {
+ val listener = object : DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {
+ trySend(displayId)
+ }
+
+ override fun onDisplayRemoved(displayId: Int) {}
+ override fun onDisplayChanged(displayId: Int) {}
+ }
+
+ val handler = Handler(Looper.getMainLooper())
+ displayManager.registerDisplayListener(listener, handler)
+
+ awaitClose {
+ displayManager.unregisterDisplayListener(listener)
+ }
+ }
+
+ val displaySettings = displays.joinToString(separator = ";") { size ->
+ "${size.x}x${size.y}/$DEFAULT_DENSITY"
+ }
+
+ // Add the overlay displays
+ Settings.Global.putString(
+ InstrumentationRegistry.getInstrumentation().context.contentResolver,
+ Settings.Global.OVERLAY_DISPLAY_DEVICES, displaySettings
+ )
+ withTimeoutOrNull(TIMEOUT) {
+ displayAddedFlow.take(displays.size).collect { displayId ->
+ addedDisplays.add(displayId)
+ }
+ } ?: error("Timed out waiting for displays to be added.")
+ addedDisplays
+ }
+
+ /**
+ * Adds multiple overlay displays with default dimensions. Any existing overlay displays
+ * will be removed before adding the new ones.
+ *
+ * @param count number of displays to add.
+ * @return List of displayIds of added displays.
+ */
+ fun setupTestDisplays(count: Int): List<Int> {
+ val displays = List(count) { Point(DEFAULT_WIDTH, DEFAULT_HEIGHT) }
+ return setupTestDisplays(displays)
+ }
+
+ private fun cleanupTestDisplays() = runBlocking {
+ if (addedDisplays.isEmpty()) {
+ return@runBlocking
+ }
+
+ val displayRemovedFlow: Flow<Int> = callbackFlow {
+ val listener = object : DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {}
+ override fun onDisplayRemoved(displayId: Int) {
+ trySend(displayId)
+ }
+
+ override fun onDisplayChanged(displayId: Int) {}
+ }
+ val handler = Handler(Looper.getMainLooper())
+ displayManager.registerDisplayListener(listener, handler)
+
+ awaitClose {
+ displayManager.unregisterDisplayListener(listener)
+ }
+ }
+
+ // Remove overlay displays
+ Settings.Global.putString(
+ InstrumentationRegistry.getInstrumentation().context.contentResolver,
+ Settings.Global.OVERLAY_DISPLAY_DEVICES, null)
+
+ withTimeoutOrNull(TIMEOUT) {
+ displayRemovedFlow.take(addedDisplays.size).collect { displayId ->
+ addedDisplays.remove(displayId)
+ }
+ } ?: error("Timed out waiting for displays to be removed.")
+ }
+
+ private companion object {
+ const val DEFAULT_WIDTH = 1280
+ const val DEFAULT_HEIGHT = 720
+ const val DEFAULT_DENSITY = 160
+ const val TAG = "SimulatedConnectedDisplayTestRule"
+ val TIMEOUT = 10.seconds
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 2ef6c558b0b5..43bcc3b61124 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -58,8 +58,7 @@ public class BackProgressAnimatorTest extends ShellTestCase {
/* frameTime = */ 0,
/* progress = */ progress,
/* triggerBack = */ false,
- /* swipeEdge = */ BackEvent.EDGE_LEFT,
- /* departingAnimationTarget = */ null);
+ /* swipeEdge = */ BackEvent.EDGE_LEFT);
}
@Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 2cc52c5ab9ad..9d4cc49a7a65 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -224,8 +224,7 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() {
/* frameTime = */ 0,
/* progress = */ progress,
/* triggerBack = */ false,
- /* swipeEdge = */ BackEvent.EDGE_LEFT,
- /* departingAnimationTarget = */ null
+ /* swipeEdge = */ BackEvent.EDGE_LEFT
)
private fun createAnimationTarget(open: Boolean): RemoteAnimationTarget {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 7a7d88b80ce3..b3d2db6da6d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -804,7 +803,7 @@ public class BubbleDataTest extends ShellTestCase {
mBubbleData.setListener(mListener);
changeExpandedStateAtTime(true, 2000L);
- verifyZeroInteractions(mListener);
+ verifyNoMoreInteractions(mListener);
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
index 925ca0f1638d..25f17fe8a3c2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE;
@@ -52,6 +53,7 @@ import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestSyncExecutor;
+import com.android.wm.shell.bubbles.BubbleTransitions.DraggedBubbleIconToFullscreen;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.ShellExecutor;
@@ -136,6 +138,7 @@ public class BubbleTransitionsTest extends ShellTestCase {
when(tvtc.getTaskInfo()).thenReturn(taskInfo);
when(tv.getController()).thenReturn(tvtc);
when(mBubble.getTaskView()).thenReturn(tv);
+ when(tv.getTaskInfo()).thenReturn(taskInfo);
mRepository.add(tvtc);
return taskInfo;
}
@@ -285,4 +288,27 @@ public class BubbleTransitionsTest extends ShellTestCase {
// directly tested.
assertFalse(mTaskViewTransitions.hasPending());
}
+
+ @Test
+ public void convertDraggedBubbleToFullscreen() {
+ ActivityManager.RunningTaskInfo taskInfo = setupBubble();
+ final DraggedBubbleIconToFullscreen bt =
+ (DraggedBubbleIconToFullscreen) mBubbleTransitions
+ .startDraggedBubbleIconToFullscreen(mBubble);
+ verify(mTransitions).startTransition(anyInt(), any(), eq(bt));
+
+ final TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0);
+ final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token,
+ mock(SurfaceControl.class));
+ chg.setMode(TRANSIT_TO_FRONT);
+ chg.setTaskInfo(taskInfo);
+ info.addChange(chg);
+ info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));
+ SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ Transitions.TransitionFinishCallback finishCb = wct -> {};
+ bt.startAnimation(bt.mTransition, info, startT, finishT, finishCb);
+ verify(startT).apply();
+ assertFalse(mTaskViewTransitions.hasPending());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
index c4b9c9ba43f1..b4b96791298d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
@@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.floatThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
@@ -84,7 +83,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onMove(0, -600);
// Check that velocity up is about 5000
verify(mMotionEventListener).onUp(eq(0f), floatThat(f -> Math.round(f) == -5000));
- verifyZeroInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mMotionEventListener);
verify(mInterceptTouchRunnable).run();
}
@@ -94,8 +93,8 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 100));
mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 100));
- verifyZeroInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
@Test
@@ -107,7 +106,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verify(mMotionEventListener).onMove(100, 0);
verify(mMotionEventListener).onUp(0, 0);
- verifyZeroInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mMotionEventListener);
verify(mInterceptTouchRunnable).run();
}
@@ -119,7 +118,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verifyNoMoreInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
@Test
@@ -129,7 +128,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verify(mMotionEventListener).onCancel();
verifyNoMoreInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
private MotionEvent newEvent(int actionDown, float x, float y) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
index 3323740697f3..1472464e8143 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
@@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
@@ -105,6 +105,6 @@ public class DevicePostureControllerTest extends ShellTestCase {
int sameDevicePosture = mDevicePostureCaptor.getValue();
mDevicePostureController.onDevicePostureChanged(sameDevicePosture);
- verifyZeroInteractions(mOnDevicePostureChangedListener);
+ verifyNoMoreInteractions(mOnDevicePostureChangedListener);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index ee9d17706372..a53277a3764e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -32,7 +32,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.graphics.Insets;
import android.graphics.Point;
@@ -108,26 +108,26 @@ public class DisplayImeControllerTest extends ShellTestCase {
public void insetsControlChanged_schedulesNoWorkOnExecutor() {
Looper.prepare();
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void insetsChanged_schedulesNoWorkOnExecutor() {
Looper.prepare();
mPerDisplay.insetsChanged(insetsStateWithIme(false));
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void showInsets_schedulesNoWorkOnExecutor() {
mPerDisplay.showInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void hideInsets_schedulesNoWorkOnExecutor() {
mPerDisplay.hideInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
// With the refactor, the control's isInitiallyVisible is used to apply to the IME, therefore
@@ -135,7 +135,7 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Test
@RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void reappliesVisibilityToChangedLeash() {
- verifyZeroInteractions(mT);
+ verifyNoMoreInteractions(mT);
mPerDisplay.mImeShowing = false;
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
index 96d202ce3a85..7d1866975848 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -29,7 +29,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -145,7 +145,7 @@ public class TabletopModeControllerTest extends ShellTestCase {
mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
- verifyZeroInteractions(mOnTabletopModeChangedListener);
+ verifyNoMoreInteractions(mOnTabletopModeChangedListener);
}
// Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
index e92e243172f7..a2066dbf7a5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
@@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
@@ -157,7 +157,7 @@ public class PipAppOpsListenerTest {
opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
packageName);
- verifyZeroInteractions(mMockExecutor);
+ verifyNoMoreInteractions(mMockExecutor);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
index 4cdb1e5b5d10..25dbc64f83de 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP;
+import static com.android.wm.shell.Flags.FLAG_ENABLE_PIP2;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -119,12 +120,28 @@ public class PipDesktopStateTest {
}
@Test
- @EnableFlags(FLAG_ENABLE_CONNECTED_DISPLAYS_PIP)
+ @EnableFlags({
+ FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, FLAG_ENABLE_PIP2
+ })
public void isConnectedDisplaysPipEnabled_returnsTrue() {
assertTrue(mPipDesktopState.isConnectedDisplaysPipEnabled());
}
@Test
+ public void isPipInDesktopMode_anyDeskActive_returnsTrue() {
+ when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
+
+ assertTrue(mPipDesktopState.isPipInDesktopMode());
+ }
+
+ @Test
+ public void isPipInDesktopMode_noDeskActive_returnsFalse() {
+ when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isPipInDesktopMode());
+ }
+
+ @Test
public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java
index 1756aad8fc9b..cc23d9a75fcd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java
@@ -21,7 +21,6 @@ import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAU
import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX;
import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.graphics.Point;
@@ -41,131 +40,75 @@ import org.mockito.Mock;
*/
@RunWith(AndroidTestingRunner.class)
public class PipDoubleTapHelperTest extends ShellTestCase {
- // represents the current pip window state and has information on current
- // max, min, and normal sizes
- @Mock private PipBoundsState mBoundStateMock;
- // tied to boundsStateMock.getBounds() in setUp()
- @Mock private Rect mBoundsMock;
-
- // represents the most recent manually resized bounds
- // i.e. dimensions from the most recent pinch in/out
- @Mock private Rect mUserResizeBoundsMock;
-
- // actual dimensions of the pip screen bounds
- private static final int MAX_WIDTH = 100;
- private static final int DEFAULT_WIDTH = 40;
- private static final int MIN_WIDTH = 10;
-
- private static final int AVERAGE_WIDTH = (MAX_WIDTH + MIN_WIDTH) / 2;
-
- /**
- * Initializes mocks and assigns values for different pip screen bounds.
- */
+ @Mock private PipBoundsState mMockPipBoundsState;
+
+ // Actual dimension guidelines of the PiP bounds.
+ private static final int MAX_EDGE_SIZE = 100;
+ private static final int DEFAULT_EDGE_SIZE = 60;
+ private static final int MIN_EDGE_SIZE = 50;
+ private static final int AVERAGE_EDGE_SIZE = (MAX_EDGE_SIZE + MIN_EDGE_SIZE) / 2;
+
@Before
public void setUp() {
// define pip bounds
- when(mBoundStateMock.getMaxSize()).thenReturn(new Point(MAX_WIDTH, 20));
- when(mBoundStateMock.getMinSize()).thenReturn(new Point(MIN_WIDTH, 2));
+ when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_EDGE_SIZE, MAX_EDGE_SIZE));
+ when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(MIN_EDGE_SIZE, MIN_EDGE_SIZE));
- Rect rectMock = mock(Rect.class);
- when(rectMock.width()).thenReturn(DEFAULT_WIDTH);
- when(mBoundStateMock.getNormalBounds()).thenReturn(rectMock);
+ final Rect normalBounds = new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE);
+ when(mMockPipBoundsState.getNormalBounds()).thenReturn(normalBounds);
+ }
- when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
- when(mBoundStateMock.getBounds()).thenReturn(mBoundsMock);
+ @Test
+ public void nextSizeSpec_resizedWiderThanAverage_returnDefaultThenCustom() {
+ final int resizeEdgeSize = (MAX_EDGE_SIZE + AVERAGE_EDGE_SIZE) / 2;
+ final Rect userResizeBounds = new Rect(0, 0, resizeEdgeSize, resizeEdgeSize);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT);
+
+ // once we toggle to DEFAULT only PiP bounds state gets updated - not the user resize bounds
+ when(mMockPipBoundsState.getBounds()).thenReturn(
+ new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE));
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_CUSTOM);
+ }
+
+ @Test
+ public void nextSizeSpec_resizedSmallerThanAverage_returnMaxThenCustom() {
+ final int resizeEdgeSize = (AVERAGE_EDGE_SIZE + MIN_EDGE_SIZE) / 2;
+ final Rect userResizeBounds = new Rect(0, 0, resizeEdgeSize, resizeEdgeSize);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX);
+
+ // Once we toggle to MAX our screen size gets updated but not the user resize bounds
+ when(mMockPipBoundsState.getBounds()).thenReturn(
+ new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE));
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_CUSTOM);
}
- /**
- * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
- *
- * <p>when the user resizes the screen to a larger than the average but not the maximum width,
- * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.DEFAULT}
- */
@Test
- public void testNextScreenSize_resizedWiderThanAverage_returnDefaultThenCustom() {
- // make the user resize width in between MAX and average
- when(mUserResizeBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2);
- // make current bounds same as resized bound since no double tap yet
- when(mBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to DEFAULT state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_DEFAULT);
-
- // once we toggle to DEFAULT our screen size gets updated
- // but not the user resize bounds
- when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to CUSTOM state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_CUSTOM);
+ public void nextSizeSpec_resizedToMax_returnDefault() {
+ final Rect userResizeBounds = new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT);
}
- /**
- * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
- *
- * <p>when the user resizes the screen to a smaller than the average but not the default width,
- * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.MAX}
- */
@Test
- public void testNextScreenSize_resizedNarrowerThanAverage_returnMaxThenCustom() {
- // make the user resize width in between MIN and average
- when(mUserResizeBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2);
- // make current bounds same as resized bound since no double tap yet
- when(mBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to MAX state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_MAX);
-
- // once we toggle to MAX our screen size gets updated
- // but not the user resize bounds
- when(mBoundsMock.width()).thenReturn(MAX_WIDTH);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to CUSTOM state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_CUSTOM);
+ public void nextSizeSpec_resizedToDefault_returnMax() {
+ final Rect userResizeBounds = new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX);
}
- /**
- * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
- *
- * <p>when the user resizes the screen to exactly the maximum width
- * then we toggle to {@code PipSizeSpec.DEFAULT}
- */
@Test
- public void testNextScreenSize_resizedToMax_returnDefault() {
- // the resized width is the same as MAX_WIDTH
- when(mUserResizeBoundsMock.width()).thenReturn(MAX_WIDTH);
- // the current bounds are also at MAX_WIDTH
- when(mBoundsMock.width()).thenReturn(MAX_WIDTH);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to DEFAULT state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_DEFAULT);
+ public void nextSizeSpec_resizedToAlmostMax_returnDefault() {
+ final Rect userResizeBounds = new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE - 1);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT);
}
- /**
- * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
- *
- * <p>when the user resizes the screen to exactly the default width
- * then we toggle to {@code PipSizeSpec.MAX}
- */
@Test
- public void testNextScreenSize_resizedToDefault_returnMax() {
- // the resized width is the same as DEFAULT_WIDTH
- when(mUserResizeBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
- // the current bounds are also at DEFAULT_WIDTH
- when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to MAX state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_MAX);
+ public void nextSizeSpec_resizedToAlmostMin_returnMax() {
+ final Rect userResizeBounds = new Rect(0, 0, MIN_EDGE_SIZE, MIN_EDGE_SIZE + 1);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX);
}
}
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
index 450989dd334d..105941079095 100644
--- 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
@@ -26,28 +26,37 @@ import android.os.Binder
import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.UsesFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display
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.doReturn
+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.server.display.feature.flags.Flags as DisplayFlags
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.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,6 +69,7 @@ import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
/**
* Test class for [DesktopDisplayModeController]
@@ -68,6 +78,7 @@ import org.mockito.kotlin.whenever
*/
@SmallTest
@RunWith(TestParameterInjector::class)
+@UsesFlags(com.android.server.display.feature.flags.Flags::class)
class DesktopDisplayModeControllerTest(
@TestParameter(valuesProvider = FlagsParameterizationProvider::class)
flags: FlagsParameterization
@@ -79,6 +90,7 @@ class DesktopDisplayModeControllerTest(
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
private val inputManager = mock<InputManager>()
+ private val displayController = mock<DisplayController>()
private val mainHandler = mock<Handler>()
private lateinit var controller: DesktopDisplayModeController
@@ -90,6 +102,12 @@ class DesktopDisplayModeControllerTest(
TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
private val wallpaperToken = MockToken().token()
+ private val externalDisplay = mock<Display>()
+
+ private lateinit var extendedDisplaySettingsRestoreSession:
+ ExtendedDisplaySettingsRestoreSession
+
+ private lateinit var mockitoSession: StaticMockitoSession
init {
mSetFlagsRule.setFlagsParameterization(flags)
@@ -97,6 +115,13 @@ class DesktopDisplayModeControllerTest(
@Before
fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ extendedDisplaySettingsRestoreSession =
+ ExtendedDisplaySettingsRestoreSession(context.contentResolver)
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder())
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
.thenReturn(defaultTDA)
@@ -109,42 +134,50 @@ class DesktopDisplayModeControllerTest(
shellTaskOrganizer,
desktopWallpaperActivityTokenProvider,
inputManager,
+ displayController,
mainHandler,
)
runningTasks.add(freeformTask)
runningTasks.add(fullscreenTask)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks))
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
+ whenever(displayController.getDisplay(EXTERNAL_DISPLAY_ID)).thenReturn(externalDisplay)
setTabletModeStatus(SwitchState.UNKNOWN)
}
+ @After
+ fun tearDown() {
+ extendedDisplaySettingsRestoreSession.restore()
+ mockitoSession.finishMocking()
+ }
+
private fun testDisplayWindowingModeSwitchOnDisplayConnected(expectToSwitch: Boolean) {
defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- connectExternalDisplay()
- if (expectToSwitch) {
- // Assumes [connectExternalDisplay] properly triggered the switching transition.
- // Will verify the transition later along with [disconnectExternalDisplay].
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- }
- disconnectExternalDisplay()
+ setExtendedMode(true)
- if (expectToSwitch) {
- 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(WINDOWING_MODE_FULLSCREEN)
- assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- } else {
- verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
- }
+ connectExternalDisplay()
+ if (expectToSwitch) {
+ // Assumes [connectExternalDisplay] properly triggered the switching transition.
+ // Will verify the transition later along with [disconnectExternalDisplay].
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ }
+ disconnectExternalDisplay()
+
+ if (expectToSwitch) {
+ 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(WINDOWING_MODE_FULLSCREEN)
+ assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ } else {
+ verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
}
}
@@ -176,15 +209,10 @@ class DesktopDisplayModeControllerTest(
disconnectExternalDisplay()
}
setTabletModeStatus(tabletModeStatus)
+ setExtendedMode(param.extendedDisplayEnabled)
- ExtendedDisplaySettingsSession(
- context.contentResolver,
- if (param.extendedDisplayEnabled) 1 else 0,
- )
- .use {
- assertThat(controller.getTargetWindowingModeForDefaultDisplay())
- .isEqualTo(param.expectedWindowingMode)
- }
+ assertThat(controller.getTargetWindowingModeForDefaultDisplay())
+ .isEqualTo(param.expectedWindowingMode)
}
@Test
@@ -199,15 +227,10 @@ class DesktopDisplayModeControllerTest(
disconnectExternalDisplay()
}
setTabletModeStatus(param.tabletModeStatus)
+ setExtendedMode(param.extendedDisplayEnabled)
- ExtendedDisplaySettingsSession(
- context.contentResolver,
- if (param.extendedDisplayEnabled) 1 else 0,
- )
- .use {
- assertThat(controller.getTargetWindowingModeForDefaultDisplay())
- .isEqualTo(param.expectedWindowingMode)
- }
+ assertThat(controller.getTargetWindowingModeForDefaultDisplay())
+ .isEqualTo(param.expectedWindowingMode)
}
@Test
@@ -215,18 +238,16 @@ class DesktopDisplayModeControllerTest(
fun displayWindowingModeSwitch_existingTasksOnConnected() {
defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
+ setExtendedMode(true)
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- connectExternalDisplay()
+ 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)
- }
+ 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
@@ -236,18 +257,16 @@ class DesktopDisplayModeControllerTest(
whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
WINDOWING_MODE_FULLSCREEN
}
+ setExtendedMode(true)
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- disconnectExternalDisplay()
+ 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)
- }
+ 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() {
@@ -266,18 +285,27 @@ class DesktopDisplayModeControllerTest(
whenever(inputManager.isInTabletMode()).thenReturn(status.value)
}
- private class ExtendedDisplaySettingsSession(
- private val contentResolver: ContentResolver,
- private val overrideValue: Int,
- ) : AutoCloseable {
+ private fun setExtendedMode(enabled: Boolean) {
+ if (DisplayFlags.enableDisplayContentModeManagement()) {
+ doReturn(enabled).`when` {
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, externalDisplay)
+ }
+ } else {
+ Settings.Global.putInt(
+ context.contentResolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+ if (enabled) 1 else 0,
+ )
+ }
+ }
+
+ private class ExtendedDisplaySettingsRestoreSession(
+ private val contentResolver: ContentResolver
+ ) {
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() {
+ fun restore() {
Settings.Global.putInt(contentResolver, settingName, initialValue)
}
}
@@ -287,7 +315,8 @@ class DesktopDisplayModeControllerTest(
context: TestParameterValuesProvider.Context
): List<FlagsParameterization> {
return FlagsParameterization.allCombinationsOf(
- Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH
+ Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH,
+ DisplayFlags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT,
)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 8a5acfa70f50..695cf600b359 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -23,7 +23,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
-import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
@@ -102,7 +102,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -127,7 +127,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -135,7 +135,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -157,7 +157,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
assertThat(desktopModeEventLogger.currentSessionId.get()).isEqualTo(NO_SESSION_ID)
}
@@ -166,7 +166,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskAdded(TASK_UPDATE)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -205,7 +205,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -213,7 +213,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskRemoved(TASK_UPDATE)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -252,7 +252,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -260,7 +260,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -302,7 +302,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -346,7 +346,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -390,7 +390,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -434,7 +434,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(FocusReason.UNKNOWN.reason),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -446,7 +446,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -485,7 +485,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index b7d25b5255f8..bd37610ae65b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -83,7 +83,7 @@ import org.mockito.kotlin.same
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/**
@@ -596,7 +596,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
.logTaskRemoved(
eq(DEFAULT_TASK_UPDATE.copy(minimizeReason = MinimizeReason.MINIMIZE_BUTTON))
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -668,7 +668,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
.logTaskInfoChanged(
eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1))
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -701,7 +701,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
)
)
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -729,7 +729,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
)
)
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -753,7 +753,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
.logTaskInfoChanged(
eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2))
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
// task 2 resize
val newTaskInfo2 =
@@ -781,7 +781,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
)
)
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -892,14 +892,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
eq(taskUpdate.visibleTaskCount.toString()),
)
}
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
private fun verifyTaskRemovedAndExitLogging(exitReason: ExitReason, taskUpdate: TaskUpdate) {
assertFalse(transitionObserver.isSessionActive)
verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate))
verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason))
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
private companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt
deleted file mode 100644
index ef394d81cc57..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.desktopmode
-
-import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
-import android.os.Binder
-import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
-import android.view.WindowManager.TRANSIT_PIP
-import android.window.TransitionInfo
-import androidx.test.filters.SmallTest
-import com.android.window.flags.Flags
-import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.TestRunningTaskInfoBuilder
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-
-/**
- * Tests for [DesktopPipTransitionObserver].
- *
- * Build/Install/Run: atest WMShellUnitTests:DesktopPipTransitionObserverTest
- */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class DesktopPipTransitionObserverTest : ShellTestCase() {
-
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
- private lateinit var observer: DesktopPipTransitionObserver
-
- private val transition = Binder()
- private var onSuccessInvokedCount = 0
-
- @Before
- fun setUp() {
- observer = DesktopPipTransitionObserver()
-
- onSuccessInvokedCount = 0
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun onTransitionReady_taskInPinnedWindowingMode_onSuccessInvoked() {
- val taskId = 1
- val pipTransition = createPendingPipTransition(taskId)
- val successfulChange = createChange(taskId, WINDOWING_MODE_PINNED)
- observer.addPendingPipTransition(pipTransition)
-
- observer.onTransitionReady(
- transition = transition,
- info = TransitionInfo(
- TRANSIT_PIP, /* flags= */
- 0
- ).apply { addChange(successfulChange) },
- )
-
- assertThat(onSuccessInvokedCount).isEqualTo(1)
- }
-
- private fun createPendingPipTransition(
- taskId: Int
- ): DesktopPipTransitionObserver.PendingPipTransition {
- return DesktopPipTransitionObserver.PendingPipTransition(
- token = transition,
- taskId = taskId,
- onSuccess = { onSuccessInvokedCount += 1 },
- )
- }
-
- private fun createChange(taskId: Int, windowingMode: Int): TransitionInfo.Change {
- return TransitionInfo.Change(mock(), mock()).apply {
- taskInfo =
- TestRunningTaskInfoBuilder()
- .setTaskId(taskId)
- .setWindowingMode(windowingMode)
- .build()
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 3813f752cae8..c455205f6411 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -275,6 +275,18 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeActiveTask_excludingDesk_leavesTaskInDesk() {
+ repo.addDesk(displayId = 2, deskId = 11)
+ repo.addDesk(displayId = 3, deskId = 12)
+ repo.addTaskToDesk(displayId = 3, deskId = 12, taskId = 100, isVisible = true)
+
+ repo.removeActiveTask(taskId = 100, excludedDeskId = 12)
+
+ assertThat(repo.getActiveTaskIdsInDesk(12)).contains(100)
+ }
+
+ @Test
fun isActiveTask_nonExistingTask_returnsFalse() {
assertThat(repo.isActiveTask(99)).isFalse()
}
@@ -1225,6 +1237,36 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@Test
+ fun setTaskInPip_savedAsMinimizedPipInDisplay() {
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse()
+
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
+ }
+
+ @Test
+ fun removeTaskInPip_removedAsMinimizedPipInDisplay() {
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
+
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false)
+
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse()
+ }
+
+ @Test
+ fun setTaskInPip_multipleDisplays_bothAreInPip() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
+ repo.setTaskInPip(SECOND_DISPLAY, taskId = 2, enterPip = true)
+
+ assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
+ assertThat(repo.isTaskMinimizedPipInDisplay(SECOND_DISPLAY, taskId = 2)).isTrue()
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun addTask_deskDoesNotExists_createsDesk() {
repo.addTask(displayId = 999, taskId = 6, isVisible = true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 14af57372ed6..240140499e69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -57,6 +57,7 @@ import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableContext
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
+import android.view.Display.INVALID_DISPLAY
import android.view.DragEvent
import android.view.Gravity
import android.view.MotionEvent
@@ -264,7 +265,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
@Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
- @Mock private lateinit var desktopPipTransitionObserver: DesktopPipTransitionObserver
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var mockDisplayContext: Context
@Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler
@@ -393,7 +393,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
whenever(userProfileContexts[anyInt()]).thenReturn(context)
whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context)
- whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder())
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -458,7 +457,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
overviewToDesktopTransitionObserver,
desksOrganizer,
desksTransitionsObserver,
- desktopPipTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
@@ -3126,6 +3124,36 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_appliesTaskLimit() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ val targetDeskTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ }
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val wct = getLatestTransition()
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, targetDeskId, targetDeskTasks[0])
+ }
+
+ @Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
@@ -3194,10 +3222,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
verify(desksOrganizer).activateDesk(any(), eq(targetDeskId))
verify(desksTransitionsObserver)
.addPendingTransition(
- DeskTransition.ActivateDesk(
+ DeskTransition.ActiveDeskWithTask(
token = transition,
displayId = SECOND_DISPLAY,
deskId = targetDeskId,
+ enterTaskId = task.taskId,
)
)
}
@@ -3473,7 +3502,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() {
val task = setUpPipTask(autoEnterEnabled = true)
val handler = mock(TransitionHandler::class.java)
@@ -3488,7 +3516,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() {
val task = setUpPipTask(autoEnterEnabled = false)
whenever(
@@ -3508,90 +3535,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
- )
- fun onDesktopTaskEnteredPip_pipIsLastTask_removesWallpaper() {
- val task = setUpPipTask(autoEnterEnabled = true)
-
- controller.onDesktopTaskEnteredPip(
- taskId = task.taskId,
- deskId = DEFAULT_DISPLAY,
- displayId = task.displayId,
- taskIsLastVisibleTaskBeforePip = true,
- )
-
- // Wallpaper is moved to the back
- val wct = getLatestTransition()
- wct.assertReorder(wallpaperToken, /* toTop= */ false)
- }
-
- @Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
- Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
- )
- fun onDesktopTaskEnteredPip_pipIsLastTask_deactivatesDesk() {
- val deskId = DEFAULT_DISPLAY
- val task = setUpPipTask(autoEnterEnabled = true, deskId = deskId)
- val transition = Binder()
- whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
-
- controller.onDesktopTaskEnteredPip(
- taskId = task.taskId,
- deskId = deskId,
- displayId = task.displayId,
- taskIsLastVisibleTaskBeforePip = true,
- )
-
- verify(desksOrganizer).deactivateDesk(any(), eq(deskId))
- verify(desksTransitionsObserver)
- .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun onDesktopTaskEnteredPip_pipIsLastTask_launchesHome() {
- val task = setUpPipTask(autoEnterEnabled = true)
-
- controller.onDesktopTaskEnteredPip(
- taskId = task.taskId,
- deskId = DEFAULT_DISPLAY,
- displayId = task.displayId,
- taskIsLastVisibleTaskBeforePip = true,
- )
-
- val wct = getLatestTransition()
- wct.assertPendingIntent(launchHomeIntent(DEFAULT_DISPLAY))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun onDesktopTaskEnteredPip_pipIsNotLastTask_doesntExitDesktopMode() {
- val task = setUpPipTask(autoEnterEnabled = true)
- val deskId = DEFAULT_DISPLAY
- setUpFreeformTask(deskId = deskId) // launch another freeform task
- val transition = Binder()
- whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
-
- controller.onDesktopTaskEnteredPip(
- taskId = task.taskId,
- deskId = deskId,
- displayId = task.displayId,
- taskIsLastVisibleTaskBeforePip = false,
- )
-
- // No transition to exit Desktop mode is started
- verifyWCTNotExecuted()
- verify(desktopModeEnterExitTransitionListener, never())
- .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- verify(desksOrganizer, never()).deactivateDesk(any(), eq(deskId))
- verify(desksTransitionsObserver, never())
- .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
- }
-
- @Test
fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = true)
val transition = Binder()
@@ -7409,6 +7352,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk_invalidDisplay_dropsRequest() {
+ controller.createDesk(INVALID_DISPLAY)
+
+ verify(desksOrganizer, never()).createDesk(any(), any())
+ }
+
+ @Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
@@ -7704,12 +7655,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
return task
}
- private fun setUpPipTask(
- autoEnterEnabled: Boolean,
- displayId: Int = DEFAULT_DISPLAY,
- deskId: Int = DEFAULT_DISPLAY,
- ): RunningTaskInfo =
- setUpFreeformTask(displayId = displayId, deskId = deskId).apply {
+ private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo =
+ setUpFreeformTask().apply {
pictureInPictureParams =
PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index ec64c2fa2337..ac9a509ac6cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.os.Binder
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -29,6 +30,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IWindowContainerToken
@@ -39,6 +41,7 @@ import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
@@ -48,6 +51,8 @@ import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpape
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
+import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
@@ -85,7 +90,6 @@ class DesktopTasksTransitionObserverTest {
private val userRepositories = mock<DesktopUserRepositories>()
private val taskRepository = mock<DesktopRepository>()
private val mixedHandler = mock<DesktopMixedTransitionHandler>()
- private val pipTransitionObserver = mock<DesktopPipTransitionObserver>()
private val backAnimationController = mock<BackAnimationController>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
@@ -110,7 +114,6 @@ class DesktopTasksTransitionObserverTest {
transitions,
shellTaskOrganizer,
mixedHandler,
- pipTransitionObserver,
backAnimationController,
desktopWallpaperActivityTokenProvider,
shellInit,
@@ -333,6 +336,50 @@ class DesktopTasksTransitionObserverTest {
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun nonTopTransparentTaskOpened_clearTopTransparentTaskIdFromRepository() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val topTransparentTask = createTaskInfo(1)
+ val nonTopTransparentTask = createTaskInfo(2)
+ whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+ .thenReturn(topTransparentTask.taskId)
+
+ transitionObserver.onTransitionReady(
+ transition = mockTransition,
+ info = createOpenChangeTransition(nonTopTransparentTask),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun nonTopTransparentTaskSentToFront_clearTopTransparentTaskIdFromRepository() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val topTransparentTask = createTaskInfo(1)
+ val nonTopTransparentTask = createTaskInfo(2)
+ whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+ .thenReturn(topTransparentTask.taskId)
+
+ transitionObserver.onTransitionReady(
+ transition = mockTransition,
+ info = createToFrontTransition(nonTopTransparentTask),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
+ }
+
+ @Test
fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() {
val wallpaperTask = createWallpaperTaskInfo()
@@ -346,6 +393,56 @@ class DesktopTasksTransitionObserverTest {
verify(desktopWallpaperActivityTokenProvider).removeToken(wallpaperTask.displayId)
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun pendingPipTransitionAborted_taskRepositoryOnPipAbortedInvoked() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ val pipTransition = Binder()
+ whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = pipTransition,
+ info = createOpenChangeTransition(task, type = TRANSIT_PIP),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+ transitionObserver.onTransitionFinished(transition = pipTransition, aborted = true)
+
+ verify(taskRepository).onPipAborted(task.displayId, task.taskId)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun exitPipTransition_taskRepositoryClearTaskInPip() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createOpenChangeTransition(task, type = TRANSIT_EXIT_PIP),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun removePipTransition_taskRepositoryClearTaskInPip() {
+ val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info = createOpenChangeTransition(task, type = TRANSIT_REMOVE_PIP),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 6e7adf368155..4e2994c27729 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -54,7 +54,9 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.MockitoSession
@@ -64,7 +66,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -85,8 +87,17 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
@Mock private lateinit var bubbleController: BubbleController
@Mock private lateinit var visualIndicator: DesktopModeVisualIndicator
-
- private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
+ @Mock private lateinit var dragCancelCallback: Runnable
+ @Mock
+ private lateinit var dragToDesktopStateListener:
+ DragToDesktopTransitionHandler.DragToDesktopStateListener
+
+ private val transactionSupplier = Supplier {
+ val transaction = mock<SurfaceControl.Transaction>()
+ whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction)
+ whenever(transaction.setFrameTimeline(anyLong())).thenReturn(transaction)
+ transaction
+ }
private lateinit var defaultHandler: DragToDesktopTransitionHandler
private lateinit var springHandler: SpringDragToDesktopTransitionHandler
@@ -104,7 +115,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
Optional.of(bubbleController),
transactionSupplier,
)
- .apply { setSplitScreenController(splitScreenController) }
+ .apply {
+ setSplitScreenController(splitScreenController)
+ dragToDesktopStateListener =
+ this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener
+ }
springHandler =
SpringDragToDesktopTransitionHandler(
context,
@@ -115,7 +130,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
Optional.of(bubbleController),
transactionSupplier,
)
- .apply { setSplitScreenController(splitScreenController) }
+ .apply {
+ setSplitScreenController(splitScreenController)
+ dragToDesktopStateListener =
+ this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener
+ }
mockitoSession =
ExtendedMockito.mockitoSession()
.strictness(Strictness.LENIENT)
@@ -438,7 +457,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
// No need to animate the cancel since the start animation couldn't even start.
- verifyZeroInteractions(dragAnimator)
+ verifyNoMoreInteractions(dragAnimator)
}
@Test
@@ -489,7 +508,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
// Should NOT have any transaction changes
- verifyZeroInteractions(mergedStartTransaction)
+ verifyNoMoreInteractions(mergedStartTransaction)
// Should NOT merge animation
verify(finishCallback, never()).onTransitionFinished(any())
}
@@ -706,8 +725,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
- fun mergeOtherTransition_cancelAndEndNotYetRequested_doesntInterruptsStartDrag() {
+ @DisableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_flagDisabled_cancelAndEndNotYetRequested_doesNotInterruptStartDrag() {
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
defaultHandler.onTaskResizeAnimationListener = mock()
@@ -721,6 +740,39 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_cancelAndEndNotYetRequested_interruptsStartDrag() {
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ verify(dragAnimator).cancelAnimator()
+ verify(dragCancelCallback).run()
+ verify(dragToDesktopStateListener).onTransitionInterrupted()
+ assertThat(defaultHandler.inProgress).isTrue()
+ // Doesn't finish start transition yet
+ verify(finishCallback, never()).onTransitionFinished(/* wct= */ anyOrNull())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_cancelAndEndNotYetRequested_finishesStartAfterAnimation() {
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+ mAnimatorTestRule.advanceTimeBy(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+
+ verify(finishCallback).onTransitionFinished(/* wct= */ anyOrNull())
+ assertThat(defaultHandler.inProgress).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
fun mergeOtherTransition_endDragAlreadyMerged_doesNotInterruptStartDrag() {
val startDragFinishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
@@ -795,6 +847,35 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
verify(dragAnimator, times(2)).startAnimation()
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun startCancelAnimation_otherTransitionInterruptingAfterCancelRequest_finishImmediately() {
+ val task1 = createTask()
+ val startTransition = startDrag(defaultHandler, task1)
+ val cancelTransition =
+ cancelDragToDesktopTransition(defaultHandler, CancelState.STANDARD_CANCEL)
+ mergeInterruptingTransition(mergeTarget = startTransition)
+ val cancelFinishCallback = mock<Transitions.TransitionFinishCallback>()
+ val startTransaction = mock<SurfaceControl.Transaction>()
+
+ val didAnimate =
+ defaultHandler.startAnimation(
+ transition = requireNotNull(cancelTransition),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP,
+ draggedTask = task1,
+ ),
+ startTransaction = startTransaction,
+ finishTransaction = mock(),
+ finishCallback = cancelFinishCallback,
+ )
+
+ assertThat(didAnimate).isTrue()
+ verify(startTransaction).apply()
+ verify(cancelFinishCallback).onTransitionFinished(/* wct= */ anyOrNull())
+ }
+
private fun mergeInterruptingTransition(mergeTarget: IBinder) {
defaultHandler.mergeAnimation(
transition = mock<IBinder>(),
@@ -942,7 +1023,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task, dragAnimator, visualIndicator)
+ handler.startDragToDesktopTransition(
+ task,
+ dragAnimator,
+ visualIndicator,
+ dragCancelCallback,
+ )
return token
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
index c7518d5914b4..3983bfbb2080 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
@@ -54,7 +54,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/**
@@ -111,7 +111,7 @@ class VisualIndicatorViewContainerTest : ShellTestCase() {
eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
)
// Assert fadeIn, fadeOut, and animateIndicatorType were not called.
- verifyZeroInteractions(spyViewContainer)
+ verifyNoMoreInteractions(spyViewContainer)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
index 14f9ffc52a66..2bd9afcef1bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -107,7 +107,7 @@ public class PipAlphaAnimatorTest {
});
verify(mMockStartCallback).run();
- verifyZeroInteractions(mMockEndCallback);
+ verifyNoMoreInteractions(mMockEndCallback);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
index 72c466663a56..fa7ab9521dac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -24,7 +24,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -117,7 +117,7 @@ public class PipEnterAnimatorTest {
});
verify(mMockStartCallback).run();
- verifyZeroInteractions(mMockEndCallback);
+ verifyNoMoreInteractions(mMockEndCallback);
// Check corner and shadow radii were set
verify(mMockAnimateTransaction, atLeastOnce())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
index b816f0ef041e..97133bedfa2d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
@@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -143,7 +143,7 @@ public class PipExpandAnimatorTest {
});
verify(mMockStartCallback).run();
- verifyZeroInteractions(mMockEndCallback);
+ verifyNoMoreInteractions(mMockEndCallback);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
index 23fbad05ec99..c99ca6dd7065 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
@@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.kotlin.MatchersKt.eq;
@@ -118,7 +118,7 @@ public class PipResizeAnimatorTest {
});
verify(mMockStartCallback).run();
- verifyZeroInteractions(mMockEndCallback);
+ verifyNoMoreInteractions(mMockEndCallback);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
index 5029371c3419..b6894fd0a9fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
@@ -30,7 +30,7 @@ import static org.mockito.kotlin.MatchersKt.eq;
import static org.mockito.kotlin.VerificationKt.clearInvocations;
import static org.mockito.kotlin.VerificationKt.times;
import static org.mockito.kotlin.VerificationKt.verify;
-import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions;
+import static org.mockito.kotlin.VerificationKt.verifyNoMoreInteractions;
import android.app.ActivityManager;
import android.app.PendingIntent;
@@ -176,7 +176,7 @@ public class PipTaskListenerTest {
mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
aspectRatio, action1));
- verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verifyNoMoreInteractions(mMockPipParamsChangedCallback);
}
@Test
@@ -193,7 +193,7 @@ public class PipTaskListenerTest {
clearInvocations(mMockPipParamsChangedCallback);
mPipTaskListener.onTaskInfoChanged(new ActivityManager.RunningTaskInfo());
- verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verifyNoMoreInteractions(mMockPipParamsChangedCallback);
verify(mMockPipTransitionState, times(0))
.setOnIdlePipTransitionStateRunnable(any(Runnable.class));
}
@@ -245,7 +245,7 @@ public class PipTaskListenerTest {
mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class));
- verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verifyNoMoreInteractions(mMockPipParamsChangedCallback);
}
@Test
@@ -262,7 +262,7 @@ public class PipTaskListenerTest {
clearInvocations(mMockPipParamsChangedCallback);
mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
- verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verifyNoMoreInteractions(mMockPipParamsChangedCallback);
verify(mMockPipTransitionState, times(0))
.setOnIdlePipTransitionStateRunnable(any(Runnable.class));
}
@@ -319,7 +319,7 @@ public class PipTaskListenerTest {
PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
extras);
- verifyZeroInteractions(mMockPipScheduler);
+ verifyNoMoreInteractions(mMockPipScheduler);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java
index 82cdfd52d2db..51de50da6921 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java
@@ -20,7 +20,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Flags;
@@ -82,7 +82,7 @@ public class PipUiStateChangeControllerTests {
mPipUiStateChangeController.onPipTransitionStateChanged(
PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERING_PIP, Bundle.EMPTY);
- verifyZeroInteractions(mPictureInPictureUiStateConsumer);
+ verifyNoMoreInteractions(mPictureInPictureUiStateConsumer);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 3099b0f5cf66..a122c3820dcb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX;
import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE;
@@ -44,6 +45,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -196,6 +198,73 @@ public class HomeTransitionObserverTest extends ShellTestCase {
}
@Test
+ @DisableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX})
+ public void startDragToDesktopFinished_flagDisabled_doesNotTriggerCallback()
+ throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+ when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true);
+ IBinder transition = mock(IBinder.class);
+ mHomeTransitionObserver.onTransitionReady(
+ transition,
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ false);
+
+ verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean());
+ }
+
+ @Test
+ @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX})
+ public void startDragToDesktopAborted_doesNotTriggerCallback() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+ when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true);
+ IBinder transition = mock(IBinder.class);
+ mHomeTransitionObserver.onTransitionReady(
+ transition,
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ true);
+
+ verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean());
+ }
+
+ @Test
+ @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX})
+ public void startDragToDesktopFinished_triggersCallback() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+ when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true);
+ IBinder transition = mock(IBinder.class);
+ mHomeTransitionObserver.onTransitionReady(
+ transition,
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ false);
+
+ verify(mListener).onHomeVisibilityChanged(/* isVisible= */ true);
+ }
+
+ @Test
@EnableFlags({Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE})
public void testDragTaskToBubbleOverHome_notifiesHomeIsVisible() throws RemoteException {
ActivityManager.RunningTaskInfo homeTask = createTaskInfo(1, ACTIVITY_TYPE_HOME);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
index 067dcec5d65d..b1f92411c5a3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
@@ -28,6 +28,7 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
@@ -109,7 +110,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
onTaskOpening(task, taskSurface)
assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
task.setActivityType(ACTIVITY_TYPE_UNDEFINED)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
verify(decoration).close()
@@ -165,7 +166,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
setLargeScreen(false)
setUpMockDecorationForTask(task)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index d69509faf4ec..ad3426e82805 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -51,6 +51,7 @@ import android.view.SurfaceView
import android.view.View
import android.view.ViewRootImpl
import android.view.WindowInsets.Type.statusBars
+import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp
import androidx.test.filters.SmallTest
@@ -134,7 +135,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
task.setActivityType(ACTIVITY_TYPE_UNDEFINED)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
verify(decoration).close()
@@ -149,12 +150,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
val taskSurface = SurfaceControl()
setUpMockDecorationForTask(task)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
task.setWindowingMode(WINDOWING_MODE_FREEFORM)
task.setActivityType(ACTIVITY_TYPE_STANDARD)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@@ -758,20 +759,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
}
@Test
- fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
- val toSplitScreenListenerCaptor = forClass(Function0::class.java)
- as ArgumentCaptor<Function0<Unit>>
- val decor = createOpenTaskDecoration(
- windowingMode = WINDOWING_MODE_MULTI_WINDOW,
- onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
- )
-
- toSplitScreenListenerCaptor.value.invoke()
-
- verify(decor).disposeStatusBarInputLayer()
- }
-
- @Test
fun testDecor_onClickToOpenBrowser_closeMenus() {
val openInBrowserListenerCaptor = forClass(Consumer::class.java)
as ArgumentCaptor<Consumer<Intent>>
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 a1f40fdefee9..4c9c2f14d805 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
@@ -84,6 +84,7 @@ import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
+import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
import org.junit.After
import org.mockito.Mockito
@@ -125,6 +126,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockShellController = mock<ShellController>()
protected val testShellExecutor = TestShellExecutor()
protected val mockAppHeaderViewHolderFactory = mock<AppHeaderViewHolder.Factory>()
+ protected val mockAppHandleViewHolderFactory = mock<AppHandleViewHolder.Factory>()
protected val mockRootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
protected val mockShellCommandHandler = mock<ShellCommandHandler>()
protected val mockWindowManager = mock<IWindowManager>()
@@ -222,6 +224,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockInputMonitorFactory,
transactionFactory,
mockAppHeaderViewHolderFactory,
+ mockAppHandleViewHolderFactory,
mockRootTaskDisplayAreaOrganizer,
windowDecorByTaskIdSpy,
mockInteractionJankMonitor,
@@ -331,7 +334,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(),
any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(),
- any(), any(), any())
+ any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.user).thenReturn(mockUserHandle)
@@ -353,12 +356,17 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
)
}
- protected fun onTaskChanging(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
+ protected fun onTaskChanging(
+ task: RunningTaskInfo,
+ leash: SurfaceControl = SurfaceControl(),
+ changeMode: Int
+ ) {
desktopModeWindowDecorViewModel.onTaskChanging(
task,
leash,
StubTransaction(),
- StubTransaction()
+ StubTransaction(),
+ changeMode
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 878324937f1a..f37f2fb14bea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -115,6 +115,7 @@ import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
+import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Unit;
@@ -171,6 +172,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
private static final boolean DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS = false;
+ private static final boolean DEFAULT_IS_RECENTS_TRANSITION_RUNNING = false;
+ private static final boolean DEFAULT_IS_MOVING_TO_BACK = false;
+
@Mock
private DisplayController mMockDisplayController;
@@ -191,8 +195,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private AppHeaderViewHolder.Factory mMockAppHeaderViewHolderFactory;
@Mock
+ private AppHandleViewHolder.Factory mMockAppHandleViewHolderFactory;
+ @Mock
private AppHeaderViewHolder mMockAppHeaderViewHolder;
@Mock
+ private AppHandleViewHolder mMockAppHandleViewHolder;
+ @Mock
private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
@Mock
private Supplier<SurfaceControl.Transaction> mMockTransactionSupplier;
@@ -301,6 +309,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
when(mMockAppHeaderViewHolderFactory
.create(any(), any(), any(), any(), any(), any(), any(), any(), any()))
.thenReturn(mMockAppHeaderViewHolder);
+ when(mMockAppHandleViewHolderFactory
+ .create(any(), any(), any(), any(), any()))
+ .thenReturn(mMockAppHandleViewHolder);
when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay)))
@@ -421,7 +432,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
/* shouldIgnoreCornerRadius= */ true,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
}
@@ -623,7 +636,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- /* shouldExcludeCaptionFromAppBounds */ true);
+ /* shouldExcludeCaptionFromAppBounds */ true,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
// Force consuming flags are disabled.
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
@@ -658,7 +673,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
assertThat(
@@ -737,7 +754,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -765,7 +784,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -792,7 +813,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -819,7 +842,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -845,7 +870,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -871,7 +898,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -898,7 +927,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -917,7 +948,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -944,7 +977,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -971,7 +1006,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -1002,6 +1039,65 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
assertThat(relayoutParams.mAsyncViewHost).isFalse();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX)
+ public void updateRelayoutParams_handle_movingToBack_captionNotVisible() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ DEFAULT_IS_STATUSBAR_VISIBLE,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ /* isMovingToBack= */ true);
+
+ assertThat(relayoutParams.mIsCaptionVisible).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX)
+ public void updateRelayoutParams_handle_inRecentsTransition_captionNotVisible() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ DEFAULT_IS_STATUSBAR_VISIBLE,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ /* isRecentsTransitionRunning= */ true,
+ DEFAULT_IS_MOVING_TO_BACK);
+
+ assertThat(relayoutParams.mIsCaptionVisible).isFalse();
+ }
+
@Test
public void relayout_fullscreenTask_appliesTransactionImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1633,7 +1729,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
}
private DesktopModeWindowDecoration createWindowDecoration(
@@ -1676,9 +1774,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo, mMockSurfaceControl, mMockHandler, mMainExecutor,
mMockMainCoroutineDispatcher, mMockBgCoroutineScope, mBgExecutor,
mMockChoreographer, mMockSyncQueue, mMockAppHeaderViewHolderFactory,
- mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser,
- mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier,
- WindowContainerTransaction::new, SurfaceControl::new,
+ mMockAppHandleViewHolderFactory, mMockRootTaskDisplayAreaOrganizer,
+ mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
+ mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index f984f6db13fc..2e46f6312d03 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -98,8 +98,6 @@ class HandleMenuTest : ShellTestCase() {
private lateinit var handleMenu: HandleMenu
- private val menuWidthWithElevation = MENU_WIDTH + MENU_PILL_ELEVATION
-
@Before
fun setUp() {
val mockAdditionalViewHostViewContainer = AdditionalViewHostViewContainer(
@@ -126,7 +124,6 @@ class HandleMenuTest : ShellTestCase() {
addOverride(R.dimen.desktop_mode_handle_menu_height, MENU_HEIGHT)
addOverride(R.dimen.desktop_mode_handle_menu_margin_top, MENU_TOP_MARGIN)
addOverride(R.dimen.desktop_mode_handle_menu_margin_start, MENU_START_MARGIN)
- addOverride(R.dimen.desktop_mode_handle_menu_pill_elevation, MENU_PILL_ELEVATION)
addOverride(
R.dimen.desktop_mode_handle_menu_pill_spacing_margin, MENU_PILL_SPACING_MARGIN)
}
@@ -141,7 +138,7 @@ class HandleMenuTest : ShellTestCase() {
assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of display.
- val expected = Point(DISPLAY_BOUNDS.centerX() - menuWidthWithElevation / 2, MENU_TOP_MARGIN)
+ val expected = Point(DISPLAY_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@@ -165,7 +162,7 @@ class HandleMenuTest : ShellTestCase() {
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of split left task.
val expected = Point(
- SPLIT_LEFT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ SPLIT_LEFT_BOUNDS.centerX() - MENU_WIDTH / 2,
MENU_TOP_MARGIN
)
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
@@ -180,7 +177,7 @@ class HandleMenuTest : ShellTestCase() {
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of split right task.
val expected = Point(
- SPLIT_RIGHT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ SPLIT_RIGHT_BOUNDS.centerX() - MENU_WIDTH / 2,
MENU_TOP_MARGIN
)
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
@@ -323,7 +320,6 @@ class HandleMenuTest : ShellTestCase() {
private const val MENU_HEIGHT = 400
private const val MENU_TOP_MARGIN = 10
private const val MENU_START_MARGIN = 20
- private const val MENU_PILL_ELEVATION = 2
private const val MENU_PILL_SPACING_MARGIN = 4
private const val HANDLE_WIDTH = 80
private const val APP_NAME = "Test App"
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 a6b077037b86..0798613ed632 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
@@ -559,6 +559,17 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testClose() = runOnUiThread {
+ verify(mockDisplayController, times(1))
+ .addDisplayWindowListener(eq(taskPositioner))
+
+ taskPositioner.close()
+
+ verify(mockDisplayController, times(1))
+ .removeDisplayWindowListener(eq(taskPositioner))
+ }
+
+ @Test
fun testIsResizingOrAnimatingResizeSet() = runOnUiThread {
Assert.assertFalse(taskPositioner.isResizingOrAnimating)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
index fa3d3e4016e9..011c8f0ae17e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
@@ -52,7 +52,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@@ -216,7 +216,7 @@ class ResizeVeilTest : ShellTestCase() {
veil.hideVeil()
- verifyZeroInteractions(mockTransaction)
+ verifyNoMoreInteractions(mockTransaction)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index cda343f3538b..2e95a979220c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -826,6 +826,18 @@ public class WindowDecorationTests extends ShellTestCase {
}
@Test
+ public void testClose_withTaskDragResizerSet_callResizerClose() {
+ final TestWindowDecoration windowDecor = createWindowDecoration(
+ new TestRunningTaskInfoBuilder().build());
+ final TaskDragResizer taskDragResizer = mock(TaskDragResizer.class);
+ windowDecor.setTaskDragResizer(taskDragResizer);
+
+ windowDecor.close();
+
+ verify(taskDragResizer).close();
+ }
+
+ @Test
public void testRelayout_captionFrameChanged_insetsReapplied() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
@@ -1232,6 +1244,11 @@ public class WindowDecorationTests extends ShellTestCase {
}
@Override
+ int getCaptionViewId() {
+ return R.id.caption;
+ }
+
+ @Override
TestView inflateLayout(Context context, int layoutResId) {
if (layoutResId == R.layout.caption_layout) {
return mMockView;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
index c8ccac35d4c4..714d06211044 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
@@ -54,7 +54,7 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/**
@@ -125,7 +125,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
loader.getName(task)
- verifyZeroInteractions(
+ verifyNoMoreInteractions(
mockPackageManager,
mockIconProvider,
mockHeaderIconFactory,
@@ -165,7 +165,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
loader.getHeaderIcon(task)
- verifyZeroInteractions(mockPackageManager, mockIconProvider, mockHeaderIconFactory)
+ verifyNoMoreInteractions(mockPackageManager, mockIconProvider, mockHeaderIconFactory)
}
@Test
@@ -187,7 +187,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
loader.getVeilIcon(task)
- verifyZeroInteractions(mockPackageManager, mockIconProvider, mockVeilIconFactory)
+ verifyNoMoreInteractions(mockPackageManager, mockIconProvider, mockVeilIconFactory)
}
@Test
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
index 6089f4291f3e..f65c7efa8ca7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
@@ -28,7 +28,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.companion.virtual.VirtualDeviceManager;
@@ -56,7 +56,7 @@ public class AudioManagerUnitTest {
audioManager.playSoundEffect(FX_KEY_CLICK);
// We expect no interactions with VDM when running on default device.
- verifyZeroInteractions(mockVdm);
+ verifyNoMoreInteractions(mockVdm);
}
@Test
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index b2c1e604db7e..964268e4ad14 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -65,6 +65,7 @@ import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.text.Spanned;
@@ -621,8 +622,10 @@ public class CompanionAssociationActivity extends FragmentActivity implements
Slog.w(TAG, "Already selected.");
return;
}
- // Notify the adapter to highlight the selected item.
- mDeviceAdapter.setSelectedPosition(position);
+ // Delay highlighting the selected item by posting to the main thread.
+ // This helps avoid flicker in the user consent dialog after device selection.
+ new Handler(
+ Looper.getMainLooper()).post(() -> mDeviceAdapter.setSelectedPosition(position));
mSelectedDevice = requireNonNull(selectedDevice);
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt
index 472ffa9289a7..6dec2f999630 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.datastore
import android.content.SharedPreferences
+import android.util.Log
/** Interface of key-value store. */
interface KeyValueStore : KeyedObservable<String> {
@@ -80,6 +81,27 @@ interface KeyValueStore : KeyedObservable<String> {
fun setString(key: String, value: String?) = setValue(key, String::class.javaObjectType, value)
}
+/** Delegation of [KeyValueStore]. */
+interface KeyValueStoreDelegate : KeyValueStore, KeyedObservableDelegate<String> {
+
+ /** [KeyValueStore] to delegate. */
+ val keyValueStoreDelegate: KeyValueStore
+
+ override val keyedObservableDelegate
+ get() = keyValueStoreDelegate
+
+ override fun contains(key: String) = keyValueStoreDelegate.contains(key)
+
+ override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>) =
+ keyValueStoreDelegate.getDefaultValue(key, valueType)
+
+ override fun <T : Any> getValue(key: String, valueType: Class<T>) =
+ keyValueStoreDelegate.getValue(key, valueType) ?: getDefaultValue(key, valueType)
+
+ override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) =
+ keyValueStoreDelegate.setValue(key, valueType, value)
+}
+
/** [SharedPreferences] based [KeyValueStore]. */
interface SharedPreferencesKeyValueStore : KeyValueStore {
@@ -103,11 +125,11 @@ interface SharedPreferencesKeyValueStore : KeyValueStore {
@Suppress("UNCHECKED_CAST")
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
+ val edit = sharedPreferences.edit()
if (value == null) {
- sharedPreferences.edit().remove(key).apply()
+ edit.remove(key).apply()
return
}
- val edit = sharedPreferences.edit()
when (valueType) {
Boolean::class.javaObjectType -> edit.putBoolean(key, value as Boolean)
Float::class.javaObjectType -> edit.putFloat(key, value as Float)
@@ -115,7 +137,7 @@ interface SharedPreferencesKeyValueStore : KeyValueStore {
Long::class.javaObjectType -> edit.putLong(key, value as Long)
String::class.javaObjectType -> edit.putString(key, value as String)
Set::class.javaObjectType -> edit.putStringSet(key, value as Set<String>)
- else -> {}
+ else -> Log.e(LOG_TAG, "Unsupported $valueType for $key: $value")
}
edit.apply()
}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index 07b1c9e3385e..ff58bf7b8728 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -116,8 +116,28 @@ interface KeyedObservable<K> {
}
/** Delegation of [KeyedObservable]. */
-open class KeyedObservableDelegate<K>(delegate: KeyedObservable<K>) :
- KeyedObservable<K> by delegate
+interface KeyedObservableDelegate<K> : KeyedObservable<K> {
+
+ /** [KeyedObservable] to delegate. */
+ val keyedObservableDelegate: KeyedObservable<K>
+
+ override fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean =
+ keyedObservableDelegate.addObserver(observer, executor)
+
+ override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean =
+ keyedObservableDelegate.addObserver(key, observer, executor)
+
+ override fun removeObserver(observer: KeyedObserver<K?>): Boolean =
+ keyedObservableDelegate.removeObserver(observer)
+
+ override fun removeObserver(key: K, observer: KeyedObserver<K>): Boolean =
+ keyedObservableDelegate.removeObserver(key, observer)
+
+ override fun notifyChange(reason: Int): Unit = keyedObservableDelegate.notifyChange(reason)
+
+ override fun notifyChange(key: K, reason: Int): Unit =
+ keyedObservableDelegate.notifyChange(key, reason)
+}
/** A thread safe implementation of [KeyedObservable]. */
open class KeyedDataObservable<K> : KeyedObservable<K> {
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 7bd4b3f771ab..ddd9d2acdab3 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -112,26 +112,6 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen
if (mSwitch.getVisibility() == VISIBLE) {
mSwitch.setOnCheckedChangeListener(this);
}
-
- setChecked(mSwitch.isChecked());
-
- if (attrs != null) {
- final TypedArray a = context.obtainStyledAttributes(attrs,
- androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/,
- 0 /*defStyleRes*/);
- final CharSequence title = a.getText(
- androidx.preference.R.styleable.Preference_android_title);
- setTitle(title);
- //TODO(b/369470034): update to next version
- if (isExpressive && Build.VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) {
- CharSequence summary = a.getText(
- androidx.preference.R.styleable.Preference_android_summary);
- setSummary(summary);
- }
- a.recycle();
- }
-
- setBackground(mSwitch.isChecked());
}
@Override
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt
index 77da98cec905..6d580fb47160 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt
@@ -18,6 +18,13 @@ package com.android.settingslib.metadata
import android.content.Context
+/** Returns the preference screen title. */
+fun PreferenceScreenMetadata.getPreferenceScreenTitle(context: Context): CharSequence? =
+ when {
+ screenTitle != 0 -> context.getString(screenTitle)
+ else -> getScreenTitle(context) ?: (this as? PreferenceTitleProvider)?.getTitle(context)
+ }
+
/** Returns the preference title. */
fun PreferenceMetadata.getPreferenceTitle(context: Context): CharSequence? =
when {
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 604acaf2a380..8896af47a1c2 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -32,6 +32,7 @@ import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceScreenMetadata
import com.android.settingslib.metadata.getPreferenceIcon
+import com.android.settingslib.metadata.getPreferenceScreenTitle
import com.android.settingslib.metadata.getPreferenceSummary
import com.android.settingslib.metadata.getPreferenceTitle
@@ -77,17 +78,22 @@ interface PreferenceBinding {
preference.icon = null
}
val isPreferenceScreen = preference is PreferenceScreen
+ val screenMetadata = this as? PreferenceScreenMetadata
// extras
preference.peekExtras()?.clear()
extras(context)?.let { preference.extras.putAll(it) }
- if (!isPreferenceScreen && this is PreferenceScreenMetadata) {
+ if (!isPreferenceScreen && screenMetadata != null) {
val extras = preference.extras
// Pass the preference key to fragment, so that the fragment could find associated
// preference screen registered in PreferenceScreenRegistry
extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key)
- arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) }
+ screenMetadata.arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) }
}
- preference.title = getPreferenceTitle(context)
+ preference.title =
+ when {
+ isPreferenceScreen -> screenMetadata?.getPreferenceScreenTitle(context)
+ else -> getPreferenceTitle(context)
+ }
if (!isPreferenceScreen) {
preference.summary = getPreferenceSummary(context)
}
@@ -100,7 +106,7 @@ interface PreferenceBinding {
// IllegalStateException when call Preference.setDependency
preference.dependency = null
if (!isPreferenceScreen) { // avoid recursive loop when build graph
- preference.fragment = (this as? PreferenceScreenMetadata)?.fragmentClass()?.name
+ preference.fragment = screenMetadata?.fragmentClass()?.name
preference.intent = intent(context)
}
if (preference is DialogPreference) {
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
index 6287fda86a73..33b614e19bbe 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
@@ -60,7 +60,6 @@ open class DefaultPreferenceBindingFactory : PreferenceBindingFactory {
?: when (metadata) {
is SwitchPreference -> SwitchPreferenceBinding.INSTANCE
is PreferenceCategory -> PreferenceCategoryBinding.INSTANCE
- is PreferenceScreenCreator -> PreferenceScreenBinding.INSTANCE
is MainSwitchPreference -> MainSwitchPreferenceBinding.INSTANCE
else -> DefaultPreferenceBinding
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index a0776822336b..71c46fa76aed 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -19,38 +19,11 @@ package com.android.settingslib.preference
import android.content.Context
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
-import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.TwoStatePreference
import com.android.settingslib.metadata.PreferenceMetadata
-import com.android.settingslib.metadata.PreferenceScreenMetadata
-import com.android.settingslib.metadata.PreferenceTitleProvider
import com.android.settingslib.widget.MainSwitchPreference
-/** Binding of preference group associated with [PreferenceCategory]. */
-interface PreferenceScreenBinding : PreferenceBinding {
-
- override fun bind(preference: Preference, metadata: PreferenceMetadata) {
- super.bind(preference, metadata)
- if (preference is PreferenceScreen) {
- val context = preference.context
- val screenMetadata = metadata as PreferenceScreenMetadata
- val screenTitle = screenMetadata.screenTitle
- preference.title =
- if (screenTitle != 0) {
- context.getString(screenTitle)
- } else {
- screenMetadata.getScreenTitle(context)
- ?: (screenMetadata as? PreferenceTitleProvider)?.getTitle(context)
- }
- }
- }
-
- companion object {
- @JvmStatic val INSTANCE = object : PreferenceScreenBinding {}
- }
-}
-
/** Binding of preference category associated with [PreferenceCategory]. */
interface PreferenceCategoryBinding : PreferenceBinding {
diff --git a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreferenceBinding.kt
index a64e8cc07b15..348941335311 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreferenceBinding.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:Suppress("ktlint:standard:filename") // remove once we have more bindings
package com.android.settingslib
@@ -29,7 +28,8 @@ interface PrimarySwitchPreferenceBinding : PreferenceBinding {
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
- (preference as PrimarySwitchPreference).apply {
+ // Could bind on PreferenceScreen
+ (preference as? PrimarySwitchPreference)?.apply {
isChecked = preferenceDataStore!!.getBoolean(key, false)
isSwitchEnabled = isEnabled
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 9d979019be58..bf6006b1eddc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -151,6 +151,18 @@ public class RestrictedPreferenceHelper {
UserHandle.myUserId());
}
+ /**
+ * Configures the user restriction that this preference will track. This is equivalent to
+ * specifying {@link R.styleable#RestrictedPreference_userRestriction} in XML and allows
+ * configuring user restriction at runtime.
+ */
+ public void setUserRestriction(@Nullable String userRestriction) {
+ mAttrUserRestriction = userRestriction == null ||
+ RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, userRestriction,
+ UserHandle.myUserId()) ? null : userRestriction;
+ setDisabledByAdmin(checkRestrictionEnforced());
+ }
+
public void useAdminDisabledSummary(boolean useSummary) {
mDisabledSummary = useSummary;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 3646842d36ef..5f88bcd8d65d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1863,10 +1863,31 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
+ " mIsLeAudioProfileConnectedFail=" + mIsLeAudioProfileConnectedFail
+ " mIsHeadsetProfileConnectedFail=" + mIsHeadsetProfileConnectedFail
+ " isConnectedSapDevice()=" + isConnectedSapDevice());
-
- return mIsA2dpProfileConnectedFail || mIsHearingAidProfileConnectedFail
- || (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail)
- || mIsLeAudioProfileConnectedFail;
+ if (mIsA2dpProfileConnectedFail) {
+ A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+ if (a2dpProfile != null && a2dpProfile.isEnabled(mDevice)) {
+ return true;
+ }
+ }
+ if (mIsHearingAidProfileConnectedFail) {
+ HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
+ if (hearingAidProfile != null && hearingAidProfile.isEnabled(mDevice)) {
+ return true;
+ }
+ }
+ if (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail) {
+ HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile();
+ if (headsetProfile != null && headsetProfile.isEnabled(mDevice)) {
+ return true;
+ }
+ }
+ if (mIsLeAudioProfileConnectedFail) {
+ LeAudioProfile leAudioProfile = mProfileManager.getLeAudioProfile();
+ if (leAudioProfile != null && leAudioProfile.isEnabled(mDevice)) {
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index a861f6fb158c..01d8694256f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -89,11 +89,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
"com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
public static final String ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED =
"com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_DEVICE_CONNECTED";
+ public static final String ACTION_LE_AUDIO_PRIVATE_BROADCAST_RECEIVED =
+ "com.android.settings.action.BLUETOOTH_LE_AUDIO_PRIVATE_BROADCAST_RECEIVED";
public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE";
public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING";
public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING";
+ public static final String EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA = "RECEIVE_DATA";
public static final String BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID =
"bluetooth_le_broadcast_primary_device_group_id";
public static final int BROADCAST_STATE_UNKNOWN = 0;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt
new file mode 100644
index 000000000000..a284d2010195
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.bluetooth
+
+import android.bluetooth.BluetoothDevice
+import android.os.Parcel
+import android.os.Parcelable
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED
+
+/**
+ * Data class representing information received in a private broadcast.
+ * This class encapsulates details about the sink device, source ID, broadcast ID, and the
+ * broadcast source state.
+ *
+ * @param sink The [BluetoothDevice] acting as the sink.
+ * @param sourceId The ID of the audio source.
+ * @param broadcastId The ID of the broadcast source.
+ * @param programInfo The program info string of the broadcast source.
+ * @param state The current state of the broadcast source.
+ */
+data class PrivateBroadcastReceiveData(
+ val sink: BluetoothDevice?,
+ val sourceId: Int = -1,
+ val broadcastId: Int = -1,
+ val programInfo: String = "",
+ val state: LocalBluetoothLeBroadcastSourceState?,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeParcelable(sink, flags)
+ parcel.writeInt(sourceId)
+ parcel.writeInt(broadcastId)
+ parcel.writeString(programInfo)
+ parcel.writeSerializable(state)
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator<PrivateBroadcastReceiveData> =
+ object : Parcelable.Creator<PrivateBroadcastReceiveData> {
+ override fun createFromParcel(parcel: Parcel) =
+ parcel.run {
+ PrivateBroadcastReceiveData(
+ sink = readParcelable(
+ BluetoothDevice::class.java.classLoader,
+ BluetoothDevice::class.java
+ ),
+ sourceId = readInt(),
+ broadcastId = readInt(),
+ programInfo = readString() ?: "",
+ state = readSerializable(
+ LocalBluetoothLeBroadcastSourceState::class.java.classLoader,
+ LocalBluetoothLeBroadcastSourceState::class.java
+ )
+ )
+ }
+ override fun newArray(size: Int): Array<PrivateBroadcastReceiveData?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ fun PrivateBroadcastReceiveData.isValid(): Boolean {
+ return sink != null
+ && sourceId != -1
+ && broadcastId != -1
+ && (state == STREAMING
+ || state == PAUSED
+ || state == DECRYPTION_FAILED)
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index ed53d8d04988..f57ee0c0930e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -182,6 +182,8 @@ public class CachedBluetoothDeviceTest {
updateProfileStatus(connectingProfile, BluetoothProfile.STATE_CONNECTING);
// Set connection policy
when(connectingProfile.getConnectionPolicy(mDevice)).thenReturn(connectionPolicy);
+ when(connectingProfile.isEnabled(mDevice))
+ .thenReturn(connectionPolicy > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Act & Assert:
// Get the expected connection summary.
@@ -191,6 +193,9 @@ public class CachedBluetoothDeviceTest {
@Test
public void onProfileStateChanged_testConnectingToDisconnected_policyAllowed_problem() {
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
String connectTimeoutString = mContext.getString(R.string.profile_connect_timeout_subtext);
testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
@@ -205,6 +210,9 @@ public class CachedBluetoothDeviceTest {
@Test
public void onProfileStateChanged_testConnectingToDisconnected_policyForbidden_noProblem() {
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
@@ -217,6 +225,9 @@ public class CachedBluetoothDeviceTest {
@Test
public void onProfileStateChanged_testConnectingToDisconnected_policyUnknown_noProblem() {
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
@@ -1830,6 +1841,10 @@ public class CachedBluetoothDeviceTest {
@Test
public void getConnectionSummary_profileConnectedFail_showErrorMessage() {
final A2dpProfile profile = mock(A2dpProfile.class);
+ when(mProfileManager.getA2dpProfile()).thenReturn(profile);
+ when(profile.getConnectionPolicy(mDevice))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(profile.isEnabled(mDevice)).thenReturn(true);
mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true);
@@ -1842,6 +1857,10 @@ public class CachedBluetoothDeviceTest {
@Test
public void getTvConnectionSummary_profileConnectedFail_showErrorMessage() {
final A2dpProfile profile = mock(A2dpProfile.class);
+ when(mProfileManager.getA2dpProfile()).thenReturn(profile);
+ when(profile.getConnectionPolicy(mDevice))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(profile.isEnabled(mDevice)).thenReturn(true);
mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt
new file mode 100644
index 000000000000..5fd67a16a305
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.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.settingslib.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.os.Parcel
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState
+import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData.Companion.isValid
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class PrivateBroadcastReceiveDataTest {
+
+ @Test
+ fun parcelable() {
+ val original = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = 1,
+ broadcastId = 2,
+ programInfo = "Test Program",
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+
+ val parcel = Parcel.obtain()
+ original.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ val recreated = PrivateBroadcastReceiveData.CREATOR.createFromParcel(parcel)
+
+ assertEquals(original, recreated)
+ }
+
+ @Test
+ fun isValid_validData() {
+ val data = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = 1,
+ broadcastId = 2,
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+ assertTrue(data.isValid())
+ }
+
+ @Test
+ fun isValid_nullSink() {
+ val data = PrivateBroadcastReceiveData(
+ sink = null,
+ sourceId = 1,
+ broadcastId = 2,
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+ assertFalse(data.isValid())
+ }
+
+ @Test
+ fun isValid_invalidSourceId() {
+ val data = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = -1,
+ broadcastId = 2,
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+ assertFalse(data.isValid())
+ }
+
+ @Test
+ fun isValid_invalidBroadcastId() {
+ val data = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = 1,
+ broadcastId = -1,
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+ assertFalse(data.isValid())
+ }
+
+ @Test
+ fun isValid_nullState() {
+ val data = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = 1,
+ broadcastId = 2,
+ state = null
+ )
+ assertFalse(data.isValid())
+ }
+
+ @Test
+ fun isValid_correctStates() {
+ assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.STREAMING).isValid())
+ assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.PAUSED).isValid())
+ assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED).isValid())
+ }
+
+ private companion object {
+ const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
+
+ val sink: BluetoothDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(
+ TEST_DEVICE_ADDRESS,
+ BluetoothDevice.ADDRESS_TYPE_RANDOM
+ )
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index fc61b1e875f3..d3291b4bac17 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -118,6 +118,8 @@ public class GlobalSettings {
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
Settings.Global.Wearable.AUTO_BEDTIME_MODE,
+ Settings.Global.Wearable.GESTURE_PRIMARY_ACTION_USER_PREFERENCE,
+ Settings.Global.Wearable.GESTURE_DISMISS_ACTION_USER_PREFERENCE,
Settings.Global.FORCE_ENABLE_PSS_PROFILING,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index cf0447f9fb3a..98f5face5e96 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -124,7 +124,8 @@ public class SystemSettings {
Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
Settings.System.NOTIFICATION_COOLDOWN_ALL,
Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
- Settings.System.PREFERRED_REGION
+ Settings.System.PREFERRED_REGION,
+ Settings.System.CV_ENABLED
));
if (Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
settings.add(Settings.System.PEAK_REFRESH_RATE);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 4c6a1ba7db0a..cd6521ff0dc5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -474,5 +474,7 @@ public class GlobalSettingsValidators {
String.valueOf(
Global.Wearable.STATUS_TRAY_CONFIGURATION_SYSTEM_HIDDEN)
}));
+ VALIDATORS.put(Global.Wearable.GESTURE_PRIMARY_ACTION_USER_PREFERENCE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.GESTURE_DISMISS_ACTION_USER_PREFERENCE, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4f649ed49be3..3a584401ed72 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -271,5 +271,7 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.PREFERRED_REGION, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(System.CV_ENABLED,
+ new InclusiveIntegerRangeValidator(0, 1));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/OWNERS b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
index b0086c180cbd..78c87b389dfc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
+++ b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
@@ -1,2 +1,2 @@
-per-file WritableNamespacePrefixes.java = mpgroover@google.com,tedbauer@google.com
-per-file WritableNamespaces.java = mpgroover@google.com,tedbauer@google.com
+per-file WritableNamespacePrefixes.java = mpgroover@google.com
+per-file WritableNamespaces.java = mpgroover@google.com
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e7527dcde95c..584b21adbe77 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -3157,6 +3157,12 @@ class SettingsProtoDumpUtil {
SystemSettingsProto.Volume.MASTER_BALANCE);
p.end(volumeToken);
+ final long systemDisplayToken = p.start(SystemSettingsProto.DISPLAY);
+ dumpSetting(s, p,
+ Settings.System.CV_ENABLED,
+ SystemSettingsProto.Display.CV_ENABLED);
+ p.end(systemDisplayToken);
+
dumpSetting(s, p,
Settings.System.WHEN_TO_MAKE_WIFI_CALLS,
SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 5b48566d92f9..129949fd38b2 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -534,6 +534,7 @@ android_library {
"androidx.compose.animation_animation-graphics",
"androidx.lifecycle_lifecycle-viewmodel-compose",
"kairos",
+ "displaylib",
"aconfig_settings_flags_lib",
],
libs: [
@@ -728,6 +729,7 @@ android_library {
"Traceur-res",
"aconfig_settings_flags_lib",
"kairos",
+ "displaylib",
],
}
@@ -770,6 +772,7 @@ android_library {
"androidx.compose.runtime_runtime",
"kairos",
"kosmos",
+ "displaylib",
"testables",
"androidx.test.rules",
"platform-compat-test-rules",
diff --git a/packages/SystemUI/TEST_OWNERS b/packages/SystemUI/TEST_OWNERS
index eadc86e386cb..21faf036c8f6 100644
--- a/packages/SystemUI/TEST_OWNERS
+++ b/packages/SystemUI/TEST_OWNERS
@@ -3,3 +3,6 @@
# for restructuring and test maintenance only
saff@google.com
+
+# Work around per-file set noparent includes
+per-file *=saff@google.com
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 97c746c49cba..d0762a3797c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -25,10 +25,13 @@ import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationTarget
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardShowWhileAwakeInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.android.window.flags.Flags
@@ -63,6 +66,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
@Mock
private lateinit var keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor
@Mock private lateinit var keyguardTransitions: KeyguardTransitions
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardShowWhileAwakeInteractor: KeyguardShowWhileAwakeInteractor
+ @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Before
fun setUp() {
@@ -77,6 +83,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
keyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor,
keyguardTransitions = keyguardTransitions,
+ selectedUserInteractor = selectedUserInteractor,
+ lockPatternUtils = lockPatternUtils,
+ keyguardShowWhileAwakeInteractor = keyguardShowWhileAwakeInteractor,
)
}
@@ -236,6 +245,8 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
.whenever(keyguardDismissTransitionInteractor)
.startDismissKeyguardTransition(any(), any())
+ whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(-1)
+
underTest.onKeyguardGoingAwayRemoteAnimationStart(
transit = 0,
apps = arrayOf(mock<RemoteAnimationTarget>()),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 83bee7c66d31..fe213a6ebbf0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -458,6 +458,56 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
@Test
@DisableSceneContainer
+ fun alpha_shadeExpansionIgnoredWhenTransitioningAwayFromLockscreen() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ shadeTestUtil.setQsExpansion(0f)
+ assertThat(alpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.STARTED,
+ value = 0f,
+ ),
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.RUNNING,
+ value = 0.8f,
+ ),
+ ),
+ testScope,
+ )
+ val priorAlpha = alpha
+ shadeTestUtil.setQsExpansion(0.5f)
+ assertThat(alpha).isEqualTo(priorAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ ),
+ testScope,
+ )
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ @DisableSceneContainer
fun alphaFromShadeExpansion_doesNotEmitWhenTransitionRunning() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionSteps(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 91cb1ff266c9..9c168298b9a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -44,7 +45,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.collect.Range
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.runCurrent
@@ -101,20 +102,30 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
// immediately 0f
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
repository.sendTransitionStep(step(.2f))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
repository.sendTransitionStep(step(0.8f))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(actual).isEqualTo(0f)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun lockscreenAlphaEndsWithZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.lockscreenAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+
+ // Jump right to the end and validate the value
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(alpha).isEqualTo(0f)
}
@Test
@@ -138,21 +149,17 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
runCurrent()
// fade out
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(1f)
+ assertThat(actual).isEqualTo(1f)
repository.sendTransitionStep(step(.1f))
- runCurrent()
- Truth.assertThat(actual).isIn(Range.open(.1f, .9f))
+ assertThat(actual).isIn(Range.open(.1f, .9f))
// alpha is 1f before the full transition starts ending
repository.sendTransitionStep(step(0.8f))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index 61119cce7bc8..8592c424ca02 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -235,6 +235,19 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
verify(mediaCarousel, never()).animationTargetX = anyFloat()
}
+ @Test
+ fun testScrollingDisabled_noScroll_notDismissible() {
+ setupMediaContainer(visibleIndex = 1, showsSettingsButton = false)
+
+ mediaCarouselScrollHandler.scrollingDisabled = true
+
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel, never()).animationTargetX = anyFloat()
+ }
+
private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) {
whenever(contentContainer.childCount).thenReturn(2)
val child1: View = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 268d62952fc7..3788049256a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -41,6 +41,8 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
@@ -76,6 +78,7 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -94,6 +97,9 @@ import java.util.concurrent.Executor;
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
+ @Rule public final CheckFlagsRule checkFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock private ViewCaptureAwareWindowManager mWindowManager;
@Mock private DozeParameters mDozeParameters;
@Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 326d8ffd3c7c..3ecf302204bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -32,14 +32,12 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.res.R
import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
@@ -77,7 +75,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val applicationScope = kosmos.testScope.backgroundScope
- @Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var blurUtils: BlurUtils
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@@ -87,6 +84,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Mock private lateinit var wallpaperController: WallpaperController
@Mock private lateinit var wallpaperInteractor: WallpaperInteractor
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor
+ @Mock private lateinit var shadeModeInteractor: ShadeModeInteractor
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var appZoomOutOptional: Optional<AppZoomOut>
@Mock private lateinit var root: View
@@ -103,7 +102,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
private var statusBarState = StatusBarState.SHADE
private val maxBlur = 150
private lateinit var notificationShadeDepthController: NotificationShadeDepthController
- private val configurationController = FakeConfigurationController()
@Before
fun setup() {
@@ -133,13 +131,11 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
wallpaperInteractor,
notificationShadeWindowController,
dozeParameters,
- context,
- ResourcesSplitShadeStateController(),
+ shadeModeInteractor,
windowRootViewBlurInteractor,
appZoomOutOptional,
applicationScope,
- dumpManager,
- configurationController,
+ dumpManager
)
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
@@ -492,15 +488,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
private fun enableSplitShade() {
- setSplitShadeEnabled(true)
+ `when` (shadeModeInteractor.isSplitShade).thenReturn(true)
}
private fun disableSplitShade() {
- setSplitShadeEnabled(false)
- }
-
- private fun setSplitShadeEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_split_notification_shade, enabled)
- configurationController.notifyConfigurationChanged()
+ `when` (shadeModeInteractor.isSplitShade).thenReturn(false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 4993b5661373..b5cfc7e9080d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -573,6 +573,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).startTimeMs)
.isEqualTo(whenElapsed)
+ assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).isEventInFuture)
+ .isFalse()
}
@Test
@@ -608,6 +610,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).startTimeMs)
.isEqualTo(whenElapsed)
+ assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).isEventInFuture)
+ .isTrue()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt
index 4e92540396d3..cd9970cfa614 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt
@@ -35,55 +35,153 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ChronometerStateTest : SysuiTestCase() {
- private lateinit var mockTimeSource: MutableTimeSource
+ private lateinit var fakeTimeSource: MutableTimeSource
@Before
fun setup() {
- mockTimeSource = MutableTimeSource()
+ fakeTimeSource = MutableTimeSource()
}
@Test
- fun initialText_isCorrect() = runTest {
- val state = ChronometerState(mockTimeSource, 0L)
- assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(0))
+ fun initialText_isEventInFutureFalse_timeIsNow() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 3_000, isEventInFuture = false)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 0))
}
@Test
- fun textUpdates_withTime() = runTest {
- val startTime = 1000L
- val state = ChronometerState(mockTimeSource, startTime)
+ fun initialText_isEventInFutureFalse_timeInPast() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 1_000, isEventInFuture = false)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 2))
+ }
+
+ @Test
+ fun initialText_isEventInFutureFalse_timeInFuture() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 5_000, isEventInFuture = false)
+ // When isEventInFuture=false, eventTimeMillis needs to be in the past if we want text to
+ // show
+ assertThat(state.currentTimeText).isNull()
+ }
+
+ @Test
+ fun initialText_isEventInFutureTrue_timeIsNow() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 3_000, isEventInFuture = true)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 0))
+ }
+
+ @Test
+ fun initialText_isEventInFutureTrue_timeInFuture() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 5_000, isEventInFuture = true)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 2))
+ }
+
+ @Test
+ fun initialText_isEventInFutureTrue_timeInPast() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 1_000, isEventInFuture = true)
+ // When isEventInFuture=true, eventTimeMillis needs to be in the future if we want text to
+ // show
+ assertThat(state.currentTimeText).isNull()
+ }
+
+ @Test
+ fun textUpdates_isEventInFutureFalse_timeInPast() = runTest {
+ val eventTime = 1000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
val job = launch { state.run() }
val elapsedTime = 5000L
- mockTimeSource.time = startTime + elapsedTime
+ fakeTimeSource.time = eventTime + elapsedTime
advanceTimeBy(elapsedTime)
assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(elapsedTime / 1000))
+ val additionalTime = 6000L
+ fakeTimeSource.time += additionalTime
+ advanceTimeBy(additionalTime)
+ assertThat(state.currentTimeText)
+ .isEqualTo(formatElapsedTime((elapsedTime + additionalTime) / 1000))
+
job.cancelAndJoin()
}
@Test
- fun textUpdates_toLargerValue() = runTest {
- val startTime = 1000L
- val state = ChronometerState(mockTimeSource, startTime)
+ fun textUpdates_isEventInFutureFalse_timeChangesFromFutureToPast() = runTest {
+ val eventTime = 15_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
val job = launch { state.run() }
- val elapsedTime = 15000L
- mockTimeSource.time = startTime + elapsedTime
- advanceTimeBy(elapsedTime)
- assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(elapsedTime / 1000))
+ // WHEN the time is 5 but the eventTime is 15
+ fakeTimeSource.time = 5_000L
+ advanceTimeBy(5_000L)
+ // THEN no text is shown
+ assertThat(state.currentTimeText).isNull()
+
+ // WHEN the time advances to 40
+ fakeTimeSource.time = 40_000L
+ advanceTimeBy(35_000)
+ // THEN text is shown as 25 seconds (40 - 15)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 25))
job.cancelAndJoin()
}
@Test
- fun textUpdates_afterResettingBase() = runTest {
+ fun textUpdates_isEventInFutureTrue_timeInFuture() = runTest {
+ val eventTime = 15_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = 5_000L
+ advanceTimeBy(5_000L)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))
+
+ val additionalTime = 6000L
+ fakeTimeSource.time += additionalTime
+ advanceTimeBy(additionalTime)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 4))
+
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun textUpdates_isEventInFutureTrue_timeChangesFromFutureToPast() = runTest {
+ val eventTime = 15_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
+ val job = launch { state.run() }
+
+ // WHEN the time is 5 and the eventTime is 15
+ fakeTimeSource.time = 5_000L
+ advanceTimeBy(5_000L)
+ // THEN 10 seconds is shown
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))
+
+ // WHEN the time advances to 40 (past the event time)
+ fakeTimeSource.time = 40_000L
+ advanceTimeBy(35_000)
+ // THEN no text is shown
+ assertThat(state.currentTimeText).isNull()
+
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun textUpdates_afterResettingBase_isEventInFutureFalse() = runTest {
val initialElapsedTime = 30000L
val startTime = 50000L
- val state = ChronometerState(mockTimeSource, startTime)
+ val state = ChronometerState(fakeTimeSource, startTime, isEventInFuture = false)
val job = launch { state.run() }
- mockTimeSource.time = startTime + initialElapsedTime
+ fakeTimeSource.time = startTime + initialElapsedTime
advanceTimeBy(initialElapsedTime)
assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(initialElapsedTime / 1000))
@@ -91,15 +189,68 @@ class ChronometerStateTest : SysuiTestCase() {
val newElapsedTime = 5000L
val newStartTime = 100000L
- val newState = ChronometerState(mockTimeSource, newStartTime)
+ val newState = ChronometerState(fakeTimeSource, newStartTime, isEventInFuture = false)
val newJob = launch { newState.run() }
- mockTimeSource.time = newStartTime + newElapsedTime
+ fakeTimeSource.time = newStartTime + newElapsedTime
advanceTimeBy(newElapsedTime)
assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(newElapsedTime / 1000))
newJob.cancelAndJoin()
}
+
+ @Test
+ fun textUpdates_afterResettingBase_isEventInFutureTrue() = runTest {
+ val initialElapsedTime = 40_000L
+ val eventTime = 50_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = initialElapsedTime
+ advanceTimeBy(initialElapsedTime)
+ // Time should be 50 - 40 = 10
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))
+
+ job.cancelAndJoin()
+
+ val newElapsedTime = 75_000L
+ val newEventTime = 100_000L
+ val newState = ChronometerState(fakeTimeSource, newEventTime, isEventInFuture = true)
+ val newJob = launch { newState.run() }
+
+ fakeTimeSource.time = newElapsedTime
+ advanceTimeBy(newElapsedTime - initialElapsedTime)
+ // Time should be 100 - 75 = 25
+ assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 25))
+
+ newJob.cancelAndJoin()
+ }
+
+ @Test
+ fun textUpdates_afterResettingisEventInFuture() = runTest {
+ val initialElapsedTime = 40_000L
+ val eventTime = 50_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = initialElapsedTime
+ advanceTimeBy(initialElapsedTime)
+ // Time should be 50 - 40 = 10
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))
+
+ job.cancelAndJoin()
+
+ val newElapsedTime = 70_000L
+ val newState = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
+ val newJob = launch { newState.run() }
+
+ fakeTimeSource.time = newElapsedTime
+ advanceTimeBy(newElapsedTime - initialElapsedTime)
+ // Time should be 70 - 50 = 20
+ assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 20))
+
+ newJob.cancelAndJoin()
+ }
}
/** A fake implementation of [TimeSource] that allows the caller to set the current time */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index f06244f4f637..7135cf01394a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -931,7 +931,40 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
- fun visibleChipKeys_fourPromotedNotifs_topThreeInList() =
+ @DisableChipsModernization
+ fun visibleChipKeys_chipsModOff_threePromotedNotifs_topTwoInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("firstNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("secondNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "thirdNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ ),
+ )
+ )
+
+ assertThat(latest).containsExactly("firstNotif", "secondNotif").inOrder()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun visibleChipKeys_chipsModOn_fourPromotedNotifs_topThreeInList() =
kosmos.runTest {
val latest by collectLastValue(underTest.visibleChipKeys)
@@ -1069,7 +1102,37 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
- fun visibleChipKeys_screenRecordAndCallAndPromotedNotifs_topThreeInList() =
+ @DisableChipsModernization
+ fun visibleChipKeys_chipsModOff_screenRecordAndCallAndPromotedNotifs_topTwoInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ val callNotificationKey = "call"
+ addOngoingCallState(callNotificationKey)
+ screenRecordState.value = ScreenRecordModel.Recording
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ )
+ )
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ )
+ )
+
+ assertThat(latest)
+ .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey)
+ .inOrder()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun visibleChipKeys_chipsModOn_screenRecordAndCallAndPromotedNotifs_topThreeInList() =
kosmos.runTest {
val latest by collectLastValue(underTest.visibleChipKeys)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index 3116143504eb..893c17998a17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -376,12 +376,67 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromBigTextStyle() {
- val entry = createEntry { setStyle(BigTextStyle()) }
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ .bigText(TEST_BIG_TEXT)
+ .setBigContentTitle(TEST_BIG_CONTENT_TITLE)
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.BigText)
+ assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content?.text).isEqualTo(TEST_BIG_TEXT)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromBigTextStyle_fallbackToContentTitle() {
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ .bigText(TEST_BIG_TEXT)
+ // bigContentTitle unset
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.BigText)
+ assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(content?.text).isEqualTo(TEST_BIG_TEXT)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromBigTextStyle_fallbackToContentText() {
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ // bigText unset
+ .setBigContentTitle(TEST_BIG_CONTENT_TITLE)
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
val content = extractContent(entry)
assertThat(content).isNotNull()
assertThat(content?.style).isEqualTo(Style.BigText)
+ assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT)
}
@Test
@@ -498,6 +553,10 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
private const val TEST_CONTENT_TEXT = "content text"
private const val TEST_SHORT_CRITICAL_TEXT = "short"
+ private const val TEST_BIG_CONTENT_TITLE = "big content title"
+ private const val TEST_BIG_TEXT = "big text"
+ private const val TEST_SUMMARY_TEXT = "summary text"
+
private const val TEST_PROGRESS = 50
private const val TEST_PROGRESS_MAX = 100
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 1b8d64d5483c..4a954b391f5f 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
@@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
import com.android.systemui.statusbar.notification.row.icon.appIconProvider
@@ -80,6 +81,9 @@ 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
@@ -107,9 +111,6 @@ 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
@@ -149,6 +150,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
@Mock private lateinit var launcherApps: LauncherApps
@Mock private lateinit var shortcutManager: ShortcutManager
@Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+ @Mock private lateinit var packageDemotionInteractor: PackageDemotionInteractor
@Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
@Mock private lateinit var contextTracker: UserContextProvider
@Mock private lateinit var bubblesManager: BubblesManager
@@ -214,6 +216,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
launcherApps,
shortcutManager,
channelEditorDialogController,
+ packageDemotionInteractor,
contextTracker,
assistantFeedbackController,
Optional.of(bubblesManager),
@@ -521,6 +524,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ any<PackageDemotionInteractor>(),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -560,6 +564,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ any<PackageDemotionInteractor>(),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -597,6 +602,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ any<PackageDemotionInteractor>(),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
index 96ae07035ed2..5f817de58b40 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
@@ -64,6 +64,7 @@ import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
import com.android.systemui.statusbar.notification.row.icon.appIconProvider
@@ -105,6 +106,7 @@ class NotificationInfoTest : SysuiTestCase() {
private val onUserInteractionCallback = mock<OnUserInteractionCallback>()
private val mockINotificationManager = mock<INotificationManager>()
private val channelEditorDialogController = mock<ChannelEditorDialogController>()
+ private val packageDemotionInteractor = mock<PackageDemotionInteractor>()
private val assistantFeedbackController = mock<AssistantFeedbackController>()
@Before
@@ -871,6 +873,7 @@ class NotificationInfoTest : SysuiTestCase() {
onUserInteractionCallback: OnUserInteractionCallback = this.onUserInteractionCallback,
channelEditorDialogController: ChannelEditorDialogController =
this.channelEditorDialogController,
+ packageDemotionInteractor: PackageDemotionInteractor = this.packageDemotionInteractor,
pkg: String = TEST_PACKAGE_NAME,
notificationChannel: NotificationChannel = this.notificationChannel,
entry: NotificationEntry = this.entry,
@@ -892,6 +895,7 @@ class NotificationInfoTest : SysuiTestCase() {
iconStyleProvider,
onUserInteractionCallback,
channelEditorDialogController,
+ packageDemotionInteractor,
pkg,
notificationChannel,
entry,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 9fdfca14a5b2..af52c31b1c53 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
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.platform.test.annotations.DisableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -38,6 +39,7 @@ import android.view.ViewGroup;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -413,6 +415,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
assertTrue("when alpha is .5, menu is visible", row.isMenuVisible());
}
+ @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
@Test
public void testOnTouchMove() {
NotificationMenuRow row = Mockito.spy(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
index 5638e0b434aa..388729743f9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
@@ -48,6 +48,7 @@ import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
@@ -92,6 +93,8 @@ public class PromotedNotificationInfoTest extends SysuiTestCase {
@Mock
private ChannelEditorDialogController mChannelEditorDialogController;
@Mock
+ private PackageDemotionInteractor mPackageDemotionInteractor;
+ @Mock
private AssistantFeedbackController mAssistantFeedbackController;
@Mock
private TelecomManager mTelecomManager;
@@ -138,6 +141,7 @@ public class PromotedNotificationInfoTest extends SysuiTestCase {
mMockIconStyleProvider,
mOnUserInteractionCallback,
mChannelEditorDialogController,
+ mPackageDemotionInteractor,
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
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 ccc8be7de038..6c6ba933c03a 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
@@ -130,7 +130,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
kosmos.testScope.runTest {
// GIVEN a threshold of 100 px
val threshold = 100f
- underTest.setSwipeThresholdPx(threshold)
+ underTest.onDensityChange(
+ threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+ )
// GIVEN that targets are set and the rows are being pulled
setTargets()
@@ -150,7 +152,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
kosmos.testScope.runTest {
// GIVEN a threshold of 100 px
val threshold = 100f
- underTest.setSwipeThresholdPx(threshold)
+ underTest.onDensityChange(
+ threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+ )
// GIVEN that targets are set and the rows are being pulled
canRowBeDismissed = false
@@ -172,7 +176,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
kosmos.testScope.runTest {
// GIVEN a threshold of 100 px
val threshold = 100f
- underTest.setSwipeThresholdPx(threshold)
+ underTest.onDensityChange(
+ threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+ )
// GIVEN that targets are set and the rows are being pulled
setTargets()
@@ -192,7 +198,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
kosmos.testScope.runTest {
// GIVEN a threshold of 100 px
val threshold = 100f
- underTest.setSwipeThresholdPx(threshold)
+ underTest.onDensityChange(
+ threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+ )
// GIVEN that targets are set and the rows are being pulled
canRowBeDismissed = false
@@ -294,6 +302,29 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
assertThat(underTest.isSwipedViewRoundableSet).isFalse()
}
+ @Test
+ fun isMagneticRowDismissible_isDismissibleWhenDetached() =
+ kosmos.testScope.runTest {
+ setDetachedState()
+
+ val isDismissible = underTest.isMagneticRowSwipeDetached(swipedRow)
+ assertThat(isDismissible).isTrue()
+ }
+
+ @Test
+ fun setMagneticRowTranslation_whenDetached_belowAttachThreshold_reattaches() =
+ kosmos.testScope.runTest {
+ // GIVEN that the swiped view has been detached
+ setDetachedState()
+
+ // WHEN setting a new translation above the attach threshold
+ val translation = 50f
+ underTest.setMagneticRowTranslation(swipedRow, translation)
+
+ // THEN the swiped view reattaches magnetically and the state becomes PULLING
+ assertThat(underTest.currentState).isEqualTo(State.PULLING)
+ }
+
@After
fun tearDown() {
// We reset the manager so that all MagneticRowListener can cancel all animations
@@ -302,7 +333,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
private fun setDetachedState() {
val threshold = 100f
- underTest.setSwipeThresholdPx(threshold)
+ underTest.onDensityChange(
+ threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+ )
// Set the pulling state
setTargets()
@@ -327,8 +360,8 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener {
val delegate = this
return object : MagneticRowListener {
- override fun setMagneticTranslation(translation: Float) {
- delegate.setMagneticTranslation(translation)
+ override fun setMagneticTranslation(translation: Float, trackEagerly: Boolean) {
+ delegate.setMagneticTranslation(translation, trackEagerly)
}
override fun triggerMagneticForce(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 789701f5e4b0..de48f4018989 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -49,6 +49,7 @@ import android.view.ViewConfiguration;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -362,6 +363,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
verify(mSwipeHelper, times(1)).isFalseGesture();
}
+ @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
@Test
public void testIsDismissGesture_farEnough() {
doReturn(false).when(mSwipeHelper).isFalseGesture();
@@ -374,6 +376,20 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
verify(mSwipeHelper, times(1)).isFalseGesture();
}
+ @EnableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
+ @Test
+ public void testIsDismissGesture_magneticSwipeIsDismissible() {
+ doReturn(false).when(mSwipeHelper).isFalseGesture();
+ doReturn(false).when(mSwipeHelper).swipedFarEnough();
+ doReturn(false).when(mSwipeHelper).swipedFastEnough();
+ doReturn(true).when(mCallback).isMagneticViewDetached(any());
+ when(mCallback.canChildBeDismissedInDirection(any(), anyBoolean())).thenReturn(true);
+ when(mEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_UP);
+
+ assertTrue("Should be a dismissal", mSwipeHelper.isDismissGesture(mEvent));
+ verify(mSwipeHelper, times(1)).isFalseGesture();
+ }
+
@Test
public void testIsDismissGesture_notFarOrFastEnough() {
doReturn(false).when(mSwipeHelper).isFalseGesture();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 9a0b8125fb25..b8be34378891 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -282,6 +282,33 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.TONAL_SPOT);
}
+ @Test
+ @HardwareColors(color = "BLK", options = {
+ "BLK|MONOCHROMATIC|#FF0000",
+ "*|VIBRANT|home_wallpaper"
+ })
+ @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES)
+ public void start_checkHardwareColor_storeInSecureSetting() {
+ // getWallpaperColors should not be called
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainExecutor).execute(registrationRunnable.capture());
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager, never()).getWallpaperColors(anyInt());
+
+ assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.MONOCHROMATIC);
+ assertThat(mThemeOverlayController.mCurrentColors.get(0).getMainColors().get(
+ 0).toArgb()).isEqualTo(Color.RED);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings).putStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+ anyInt());
+
+ assertThat(updatedSetting.getValue().contains(
+ "android.theme.customization.theme_style\":\"MONOCHROMATIC")).isTrue();
+ assertThat(updatedSetting.getValue().contains(
+ "android.theme.customization.system_palette\":\"ffff0000")).isTrue();
+ }
@Test
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 763b1072f968..f2f177356fab 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -572,10 +572,6 @@ constructor(
}
fun handleFidgetTap(x: Float, y: Float) {
- if (!com.android.systemui.Flags.clockFidgetAnimation()) {
- return
- }
-
clock?.run {
smallClock.animations.onFidgetTap(x, y)
largeClock.animations.onFidgetTap(x, y)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d84b034eade8..60ec051315d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -124,7 +124,6 @@ import com.android.settingslib.Utils;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -301,7 +300,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt
private final Provider<SceneInteractor> mSceneInteractor;
private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor;
private final Provider<CommunalSceneInteractor> mCommunalSceneInteractor;
- private final KeyguardServiceShowLockscreenInteractor mKeyguardServiceShowLockscreenInteractor;
+ private final Provider<KeyguardServiceShowLockscreenInteractor>
+ mKeyguardServiceShowLockscreenInteractor;
private final AuthController mAuthController;
private final UiEventLogger mUiEventLogger;
private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
@@ -2219,7 +2219,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt
Provider<JavaAdapter> javaAdapter,
Provider<SceneInteractor> sceneInteractor,
Provider<CommunalSceneInteractor> communalSceneInteractor,
- KeyguardServiceShowLockscreenInteractor keyguardServiceShowLockscreenInteractor) {
+ Provider<KeyguardServiceShowLockscreenInteractor>
+ keyguardServiceShowLockscreenInteractor) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2553,7 +2554,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt
if (KeyguardWmStateRefactor.isEnabled()) {
mJavaAdapter.get().alwaysCollectFlow(
- mKeyguardServiceShowLockscreenInteractor.getShowNowEvents(),
+ mKeyguardServiceShowLockscreenInteractor.get().getShowNowEvents(),
this::onKeyguardServiceShowLockscreenNowEvents
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index c78f75a334fd..e3afe2e190e9 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -778,18 +778,26 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
protected boolean swipedFarEnough() {
float translation = getTranslation(mTouchedView);
- return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
- mTouchedView);
+ return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mTouchedView);
}
public boolean isDismissGesture(MotionEvent ev) {
float translation = getTranslation(mTouchedView);
return ev.getActionMasked() == MotionEvent.ACTION_UP
&& !mFalsingManager.isUnlockingDisabled()
- && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough())
+ && !isFalseGesture() && isSwipeDismissible()
&& mCallback.canChildBeDismissedInDirection(mTouchedView, translation > 0);
}
+ /** Can the swipe gesture on the touched view be considered as a dismiss intention */
+ public boolean isSwipeDismissible() {
+ if (magneticNotificationSwipes()) {
+ return mCallback.isMagneticViewDetached(mTouchedView) || swipedFastEnough();
+ } else {
+ return swipedFastEnough() || swipedFarEnough();
+ }
+ }
+
/** Returns true if the gesture should be rejected. */
public boolean isFalseGesture() {
boolean falsingDetected = mCallback.isAntiFalsingNeeded();
@@ -970,6 +978,13 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
void onMagneticInteractionEnd(View view, float velocity);
/**
+ * Determine if a view managed by magnetic interactions is magnetically detached
+ * @param view The magnetic view
+ * @return if the view is detached according to its magnetic state.
+ */
+ boolean isMagneticViewDetached(View view);
+
+ /**
* Called when the child is long pressed and available to start drag and drop.
*
* @param v the view that was long pressed.
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 857fa5cac3e2..756edb3d048d 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
@@ -247,7 +247,7 @@ constructor(
showsOnlyActiveMedia = false
}
falsingProtectionNeeded = false
- disablePagination = true
+ disableScrolling = true
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 721d116004f3..101e8cc23f17 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -27,6 +27,7 @@ import android.os.Handler
import android.util.Log
import android.view.Display
import android.view.IWindowManager
+import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib
import com.android.app.tracing.FlowTracing.traceEach
import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
@@ -60,7 +61,7 @@ import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn
/** Repository for providing access to display related information and events. */
-interface DisplayRepository {
+interface DisplayRepository : DisplayRepositoryFromLib {
/** Display change event indicating a change to the given displayId has occurred. */
val displayChangeEvent: Flow<Int>
@@ -74,13 +75,6 @@ interface DisplayRepository {
val displayIdsWithSystemDecorations: StateFlow<Set<Int>>
/**
- * Provides the current set of displays.
- *
- * Consider using [displayIds] if only the [Display.getDisplayId] is needed.
- */
- val displays: StateFlow<Set<Display>>
-
- /**
* Provides the current set of display ids.
*
* Note that it is preferred to use this instead of [displays] if only the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 757464976261..79685088fed7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -673,7 +673,8 @@ public class KeyguardService extends Service {
if (SceneContainerFlag.isEnabled()) {
mDeviceEntryInteractorLazy.get().lockNow("doKeyguardTimeout");
} else if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardServiceShowLockscreenInteractor.onKeyguardServiceDoKeyguardTimeout();
+ mKeyguardServiceShowLockscreenInteractor
+ .onKeyguardServiceDoKeyguardTimeout(options);
}
mKeyguardViewMediator.doKeyguardTimeout(options);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 58692746d1e0..51b953ef290c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -22,11 +22,14 @@ import android.util.Log
import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationTarget
import android.view.WindowManager
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardShowWhileAwakeInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.window.flags.Flags
import com.android.wm.shell.keyguard.KeyguardTransitions
import java.util.concurrent.Executor
@@ -46,6 +49,9 @@ constructor(
private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
private val keyguardTransitions: KeyguardTransitions,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardShowWhileAwakeInteractor: KeyguardShowWhileAwakeInteractor,
) {
/**
@@ -92,12 +98,23 @@ constructor(
* second timeout).
*/
private var isKeyguardGoingAway = false
- private set(value) {
+ private set(goingAway) {
// TODO(b/278086361): Extricate the keyguard state controller.
- keyguardStateController.notifyKeyguardGoingAway(value)
- field = value
+ keyguardStateController.notifyKeyguardGoingAway(goingAway)
+
+ if (goingAway) {
+ keyguardGoingAwayRequestedForUserId = selectedUserInteractor.getSelectedUserId()
+ }
+
+ field = goingAway
}
+ /**
+ * The current user ID when we asked WM to start the keyguard going away animation. This is used
+ * for validation when user switching occurs during unlock.
+ */
+ private var keyguardGoingAwayRequestedForUserId: Int = -1
+
/** Callback provided by WM to call once we're done with the going away animation. */
private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
@@ -171,6 +188,14 @@ constructor(
nonApps: Array<RemoteAnimationTarget>,
finishedCallback: IRemoteAnimationFinishedCallback,
) {
+ goingAwayRemoteAnimationFinishedCallback = finishedCallback
+
+ if (maybeStartTransitionIfUserSwitchedDuringGoingAway()) {
+ Log.d(TAG, "User switched during keyguard going away - ending remote animation.")
+ endKeyguardGoingAwayAnimation()
+ return
+ }
+
// If we weren't expecting the keyguard to be going away, WM triggered this transition.
if (!isKeyguardGoingAway) {
// Since WM triggered this, we're likely not transitioning to GONE yet. See if we can
@@ -198,7 +223,6 @@ constructor(
}
if (apps.isNotEmpty()) {
- goingAwayRemoteAnimationFinishedCallback = finishedCallback
keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
} else {
// Nothing to do here if we have no apps, end the animation, which will cancel it and WM
@@ -211,6 +235,7 @@ constructor(
// If WM cancelled the animation, we need to end immediately even if we're still using the
// animation.
endKeyguardGoingAwayAnimation()
+ maybeStartTransitionIfUserSwitchedDuringGoingAway()
}
/**
@@ -301,6 +326,29 @@ constructor(
}
}
+ /**
+ * If necessary, start a transition to show/hide keyguard in response to a user switch during
+ * keyguard going away.
+ *
+ * Returns [true] if a transition was started, or false if a transition was not necessary.
+ */
+ private fun maybeStartTransitionIfUserSwitchedDuringGoingAway(): Boolean {
+ val currentUser = selectedUserInteractor.getSelectedUserId()
+ if (currentUser != keyguardGoingAwayRequestedForUserId) {
+ if (lockPatternUtils.isSecure(currentUser)) {
+ keyguardShowWhileAwakeInteractor.onSwitchedToSecureUserWhileKeyguardGoingAway()
+ } else {
+ keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
+ reason = "User switch during keyguard going away, and new user is insecure"
+ )
+ }
+
+ return true
+ } else {
+ return false
+ }
+ }
+
companion object {
private val TAG = "WindowManagerLsVis"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt
new file mode 100644
index 000000000000..16c2d14b78ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.os.IRemoteCallback
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Holds an IRemoteCallback along with the current user ID at the time the callback was provided.
+ */
+data class ShowLockscreenCallback(val userId: Int, val remoteCallback: IRemoteCallback)
+
+/** Maintains state related to KeyguardService requests to show the lockscreen. */
+@SysUISingleton
+class KeyguardServiceShowLockscreenRepository @Inject constructor() {
+ val showLockscreenCallbacks = ArrayList<ShowLockscreenCallback>()
+
+ /**
+ * Adds a callback that we'll notify when we show the lockscreen (or affirmatively decide not to
+ * show it).
+ */
+ fun addShowLockscreenCallback(forUser: Int, callback: IRemoteCallback) {
+ synchronized(showLockscreenCallbacks) {
+ showLockscreenCallbacks.add(ShowLockscreenCallback(forUser, callback))
+ }
+ }
+
+ companion object {
+ private const val TAG = "ShowLockscreenRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index ab0efed2cb76..02e04aa279d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -226,6 +226,10 @@ constructor(
}
fun handleFidgetTap(x: Float, y: Float) {
+ if (!com.android.systemui.Flags.clockFidgetAnimation()) {
+ return
+ }
+
if (selectedClockSize.value == ClockSizeSetting.DYNAMIC) {
clockEventController.handleFidgetTap(x, y)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt
index b55bb383c308..07a31e16384c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt
@@ -17,16 +17,27 @@
package com.android.systemui.keyguard.domain.interactor
import android.annotation.SuppressLint
+import android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK
+import android.os.Bundle
+import android.os.IRemoteCallback
+import android.os.RemoteException
+import android.util.Log
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardServiceShowLockscreenRepository
+import com.android.systemui.keyguard.data.repository.ShowLockscreenCallback
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
/**
- * Logic around requests by [KeyguardService] to show keyguard right now, even though the device is
- * awake and not going to sleep.
+ * Logic around requests by [KeyguardService] and friends to show keyguard right now, even though
+ * the device is awake and not going to sleep.
*
* This can happen if WM#lockNow() is called, if KeyguardService#showDismissibleKeyguard is called
* because we're folding with "continue using apps on fold" set to "swipe up to continue", or if the
@@ -38,7 +49,28 @@ import kotlinx.coroutines.launch
@SysUISingleton
class KeyguardServiceShowLockscreenInteractor
@Inject
-constructor(@Background val backgroundScope: CoroutineScope) {
+constructor(
+ @Background val backgroundScope: CoroutineScope,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val repository: KeyguardServiceShowLockscreenRepository,
+ private val userTracker: UserTracker,
+ private val wmLockscreenVisibilityInteractor: Lazy<WindowManagerLockscreenVisibilityInteractor>,
+ private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
+) : CoreStartable {
+
+ override fun start() {
+ backgroundScope.launch {
+ // Whenever we tell ATMS that lockscreen is visible, notify any showLockscreenCallbacks.
+ // This is not the only place we notify the lockNowCallbacks - there are cases where we
+ // decide not to show the lockscreen despite being asked to, and we need to notify the
+ // callback in those cases as well.
+ wmLockscreenVisibilityInteractor.get().lockscreenVisibility.collect { visible ->
+ if (visible) {
+ notifyShowLockscreenCallbacks()
+ }
+ }
+ }
+ }
/**
* Emits whenever [KeyguardService] receives a call that indicates we should show the lockscreen
@@ -57,9 +89,38 @@ constructor(@Background val backgroundScope: CoroutineScope) {
/**
* Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that
* the device locked while the screen was on.
+ *
+ * We'll show keyguard, and if provided, save the lock on user switch callback, to notify it
+ * later when we successfully show.
*/
- fun onKeyguardServiceDoKeyguardTimeout() {
+ fun onKeyguardServiceDoKeyguardTimeout(options: Bundle? = null) {
backgroundScope.launch {
+ if (options?.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) != null) {
+ val userId = userTracker.userId
+
+ // This callback needs to be invoked after we show the lockscreen (or decide not to
+ // show it) otherwise System UI will crash in 20 seconds, as a security measure.
+ repository.addShowLockscreenCallback(
+ userId,
+ IRemoteCallback.Stub.asInterface(
+ options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK)
+ ),
+ )
+
+ Log.d(
+ TAG,
+ "Showing lockscreen now - setting required callback for user $userId. " +
+ "SysUI will crash if this callback is not invoked.",
+ )
+
+ // If the keyguard is disabled or suppressed, we'll never actually show the
+ // lockscreen. Notify the callback so we don't crash.
+ if (!keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed()) {
+ Log.d(TAG, "Keyguard is disabled or suppressed, notifying callbacks now.")
+ notifyShowLockscreenCallbacks()
+ }
+ }
+
showNowEvents.emit(ShowWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON)
}
}
@@ -74,4 +135,33 @@ constructor(@Background val backgroundScope: CoroutineScope) {
showNowEvents.emit(ShowWhileAwakeReason.FOLDED_WITH_SWIPE_UP_TO_CONTINUE)
}
}
+
+ /** Notifies the callbacks that we've either locked, or decided not to lock. */
+ private fun notifyShowLockscreenCallbacks() {
+ var callbacks: MutableList<ShowLockscreenCallback>
+ synchronized(repository.showLockscreenCallbacks) {
+ callbacks = ArrayList(repository.showLockscreenCallbacks)
+ repository.showLockscreenCallbacks.clear()
+ }
+
+ val iter: MutableIterator<ShowLockscreenCallback> = callbacks.listIterator()
+ while (iter.hasNext()) {
+ val callback = iter.next()
+ iter.remove()
+ if (callback.userId != selectedUserInteractor.getSelectedUserId()) {
+ Log.i(TAG, "Not notifying lockNowCallback due to user mismatch")
+ continue
+ }
+ Log.i(TAG, "Notifying lockNowCallback")
+ try {
+ callback.remoteCallback.sendResult(null)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Could not issue LockNowCallback sendResult", e)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "ShowLockscreenInteractor"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt
index a8000a568a6a..c67939a0738a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt
@@ -16,15 +16,20 @@
package com.android.systemui.keyguard.domain.interactor
+import android.annotation.SuppressLint
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
/** The reason we're showing lockscreen while awake, used for logging. */
enum class ShowWhileAwakeReason(private val logReason: String) {
@@ -38,6 +43,9 @@ enum class ShowWhileAwakeReason(private val logReason: String) {
),
KEYGUARD_TIMEOUT_WHILE_SCREEN_ON(
"Timed out while the screen was kept on, or WM#lockNow() was called."
+ ),
+ SWITCHED_TO_SECURE_USER_WHILE_GOING_AWAY(
+ "User switch to secure user occurred during keyguardGoingAway sequence, so we're locking."
);
override fun toString(): String {
@@ -68,6 +76,7 @@ enum class ShowWhileAwakeReason(private val logReason: String) {
class KeyguardShowWhileAwakeInteractor
@Inject
constructor(
+ @Background val backgroundScope: CoroutineScope,
biometricSettingsRepository: BiometricSettingsRepository,
keyguardEnabledInteractor: KeyguardEnabledInteractor,
keyguardServiceShowLockscreenInteractor: KeyguardServiceShowLockscreenInteractor,
@@ -91,6 +100,15 @@ constructor(
.filter { reshow -> reshow }
.map { ShowWhileAwakeReason.KEYGUARD_REENABLED }
+ /**
+ * Emits whenever a user switch to a secure user occurs during keyguard going away.
+ *
+ * This is an event flow, hence the SharedFlow.
+ */
+ @SuppressLint("SharedFlowCreation")
+ val switchedToSecureUserDuringGoingAway: MutableSharedFlow<ShowWhileAwakeReason> =
+ MutableSharedFlow()
+
/** Emits whenever we should show lockscreen while the screen is on, for any reason. */
val showWhileAwakeEvents: Flow<ShowWhileAwakeReason> =
merge(
@@ -108,5 +126,15 @@ constructor(
keyguardServiceShowLockscreenInteractor.showNowEvents.filter {
keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed()
},
+ switchedToSecureUserDuringGoingAway,
)
+
+ /** A user switch to a secure user occurred while we were going away. We need to re-lock. */
+ fun onSwitchedToSecureUserWhileKeyguardGoingAway() {
+ backgroundScope.launch {
+ switchedToSecureUserDuringGoingAway.emit(
+ ShowWhileAwakeReason.SWITCHED_TO_SECURE_USER_WHILE_GOING_AWAY
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index 2b4582a2a096..780b7fb06b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -29,6 +29,7 @@ constructor(
private val auditLogger: KeyguardTransitionAuditLogger,
private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor,
private val keyguardStateCallbackInteractor: KeyguardStateCallbackInteractor,
+ private val keyguardServiceShowLockscreenInteractor: KeyguardServiceShowLockscreenInteractor,
) : CoreStartable {
override fun start() {
@@ -54,6 +55,7 @@ constructor(
auditLogger.start()
statusBarDisableFlagsInteractor.start()
keyguardStateCallbackInteractor.start()
+ keyguardServiceShowLockscreenInteractor.start()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 6d8a943d3e28..830afeac7b96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -31,10 +31,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
@@ -165,43 +163,27 @@ constructor(
.onStart { emit(false) }
.distinctUntilChanged()
- private val isOnLockscreen: Flow<Boolean> =
+ private val isOnOrGoingToLockscreen: Flow<Boolean> =
combine(
- keyguardTransitionInteractor.isFinishedIn(LOCKSCREEN).onStart { emit(false) },
- anyOf(
- keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)),
- keyguardTransitionInteractor.isInTransition(Edge.create(from = LOCKSCREEN)),
- ),
- ) { onLockscreen, transitioningToOrFromLockscreen ->
- onLockscreen || transitioningToOrFromLockscreen
+ keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it == 1f },
+ keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)),
+ ) { onLockscreen, transitioningToLockscreen ->
+ onLockscreen || transitioningToLockscreen
}
.distinctUntilChanged()
private val alphaOnShadeExpansion: Flow<Float> =
combineTransform(
- anyOf(
- keyguardTransitionInteractor.isInTransition(
- edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
- edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
- ),
- keyguardTransitionInteractor.isInTransition(
- edge = Edge.create(from = Overlays.Bouncer, to = LOCKSCREEN),
- edgeWithoutSceneContainer =
- Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
- ),
- keyguardTransitionInteractor.isInTransition(
- Edge.create(from = LOCKSCREEN, to = DREAMING)
- ),
- keyguardTransitionInteractor.isInTransition(
- Edge.create(from = LOCKSCREEN, to = OCCLUDED)
- ),
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = Overlays.Bouncer, to = LOCKSCREEN),
+ edgeWithoutSceneContainer = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
),
- isOnLockscreen,
+ isOnOrGoingToLockscreen,
shadeInteractor.qsExpansion,
shadeInteractor.shadeExpansion,
- ) { disabledTransitionRunning, isOnLockscreen, qsExpansion, shadeExpansion ->
+ ) { disabledTransitionRunning, isOnOrGoingToLockscreen, qsExpansion, shadeExpansion ->
// Fade out quickly as the shade expands
- if (isOnLockscreen && !disabledTransitionRunning) {
+ if (isOnOrGoingToLockscreen && !disabledTransitionRunning) {
val alpha =
1f -
MathUtils.constrainedMap(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 3758afa61ed4..9312bca04994 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -66,6 +66,9 @@ constructor(
transitionAnimation.sharedFlow(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
onStep = alphaForAnimationStep,
+ // Rapid swipes to bouncer, and may end up skipping intermediate values that would've
+ // caused a complete fade out of lockscreen elements. Ensure it goes to 0f.
+ onFinish = { 0f },
)
val lockscreenAlpha: Flow<Float> = shortcutsAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index ac6343c6bb64..71b3223b77be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -159,8 +159,8 @@ constructor(
/** Is the player currently visible (at the end of the transformation */
private var playersVisible: Boolean = false
- /** Are we currently disabling pagination only allowing one media session to show */
- private var currentlyDisablePagination: Boolean = false
+ /** Are we currently disabling scolling, only allowing the first media session to show */
+ private var currentlyDisableScrolling: Boolean = false
/**
* The desired location where we'll be at the end of the transformation. Usually this matches
@@ -1130,21 +1130,22 @@ constructor(
val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia ?: true
val startShowsActive =
hostStates[currentStartLocation]?.showsOnlyActiveMedia ?: endShowsActive
- val startDisablePagination = hostStates[currentStartLocation]?.disablePagination ?: false
- val endDisablePagination = hostStates[currentEndLocation]?.disablePagination ?: false
+ val startDisableScrolling = hostStates[currentStartLocation]?.disableScrolling ?: false
+ val endDisableScrolling = hostStates[currentEndLocation]?.disableScrolling ?: false
if (
currentlyShowingOnlyActive != endShowsActive ||
- currentlyDisablePagination != endDisablePagination ||
+ currentlyDisableScrolling != endDisableScrolling ||
((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
(startShowsActive != endShowsActive ||
- startDisablePagination != endDisablePagination))
+ startDisableScrolling != endDisableScrolling))
) {
// Whenever we're transitioning from between differing states or the endstate differs
// we reset the translation
currentlyShowingOnlyActive = endShowsActive
- currentlyDisablePagination = endDisablePagination
+ currentlyDisableScrolling = endDisableScrolling
mediaCarouselScrollHandler.resetTranslation(animate = true)
+ mediaCarouselScrollHandler.scrollingDisabled = currentlyDisableScrolling
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index 9cf4a7b3a007..68865d65139c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -127,6 +127,9 @@ class MediaCarouselScrollHandler(
scrollView.relativeScrollX = newRelativeScroll
}
+ /** Is scrolling disabled for the carousel */
+ var scrollingDisabled: Boolean = false
+
/** Does the dismiss currently show the setting cog? */
var showsSettingsButton: Boolean = false
@@ -270,6 +273,10 @@ class MediaCarouselScrollHandler(
}
private fun onTouch(motionEvent: MotionEvent): Boolean {
+ if (scrollingDisabled) {
+ return false
+ }
+
val isUp = motionEvent.action == MotionEvent.ACTION_UP
if (gestureDetector.onTouchEvent(motionEvent)) {
if (isUp) {
@@ -349,6 +356,10 @@ class MediaCarouselScrollHandler(
}
fun onScroll(down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean {
+ if (scrollingDisabled) {
+ return false
+ }
+
val totalX = lastMotion.x - down.x
val currentTranslation = scrollView.getContentTranslation()
if (currentTranslation != 0.0f || !scrollView.canScrollHorizontally((-totalX).toInt())) {
@@ -405,6 +416,10 @@ class MediaCarouselScrollHandler(
}
private fun onFling(vX: Float, vY: Float): Boolean {
+ if (scrollingDisabled) {
+ return false
+ }
+
if (vX * vX < 0.5 * vY * vY) {
return false
}
@@ -575,6 +590,9 @@ class MediaCarouselScrollHandler(
* @param destIndex destination index to indicate where the scroll should end.
*/
fun scrollToPlayer(sourceIndex: Int = -1, destIndex: Int) {
+ if (scrollingDisabled) {
+ return
+ }
if (sourceIndex >= 0 && sourceIndex < mediaContent.childCount) {
scrollView.relativeScrollX = sourceIndex * playerWidthPlusPadding
}
@@ -596,6 +614,9 @@ class MediaCarouselScrollHandler(
* @param step A positive number means next, and negative means previous.
*/
fun scrollByStep(step: Int) {
+ if (scrollingDisabled) {
+ return
+ }
val destIndex = visibleMediaIndex + step
if (destIndex >= mediaContent.childCount || destIndex < 0) {
if (!showsSettingsButton) return
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index a518349ea424..37af7642b105 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -295,7 +295,7 @@ class MediaHost(
changedListener?.invoke()
}
- override var disablePagination: Boolean = false
+ override var disableScrolling: Boolean = false
set(value) {
if (field == value) {
return
@@ -320,7 +320,7 @@ class MediaHost(
mediaHostState.visible = visible
mediaHostState.disappearParameters = disappearParameters.deepCopy()
mediaHostState.falsingProtectionNeeded = falsingProtectionNeeded
- mediaHostState.disablePagination = disablePagination
+ mediaHostState.disableScrolling = disableScrolling
return mediaHostState
}
@@ -349,7 +349,7 @@ class MediaHost(
if (!disappearParameters.equals(other.disappearParameters)) {
return false
}
- if (disablePagination != other.disablePagination) {
+ if (disableScrolling != other.disableScrolling) {
return false
}
return true
@@ -363,7 +363,7 @@ class MediaHost(
result = 31 * result + showsOnlyActiveMedia.hashCode()
result = 31 * result + if (visible) 1 else 2
result = 31 * result + disappearParameters.hashCode()
- result = 31 * result + disablePagination.hashCode()
+ result = 31 * result + disableScrolling.hashCode()
return result
}
}
@@ -423,10 +423,11 @@ interface MediaHostState {
var disappearParameters: DisappearParameters
/**
- * Whether pagination should be disabled for this host, meaning that when there are multiple
- * media sessions, only the first one will appear.
+ * Whether scrolling should be disabled for this host, meaning that when there are multiple
+ * media sessions, it will not be possible to scroll between media sessions or swipe away the
+ * entire media carousel. The first media session will always be shown.
*/
- var disablePagination: Boolean
+ var disableScrolling: Boolean
/** Get a copy of this view state, deepcopying all appropriate members */
fun copy(): MediaHostState
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
index 8751aa31f237..f07238895aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
@@ -73,6 +73,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -94,9 +95,12 @@ import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
@@ -106,6 +110,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastRoundToInt
import com.android.compose.PlatformButton
import com.android.compose.PlatformIconButton
import com.android.compose.PlatformOutlinedButton
@@ -134,6 +139,7 @@ import com.android.systemui.media.remedia.ui.viewmodel.MediaPlayPauseActionViewM
import com.android.systemui.media.remedia.ui.viewmodel.MediaSecondaryActionViewModel
import com.android.systemui.media.remedia.ui.viewmodel.MediaViewModel
import kotlin.math.max
+import kotlin.math.min
/**
* Renders a media controls UI element.
@@ -414,12 +420,56 @@ private fun ContentScope.CardForegroundContent(
.clip(CircleShape),
)
+ var cardMaxWidth: Int by remember { mutableIntStateOf(0) }
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
- modifier = Modifier.align(Alignment.TopEnd),
+ modifier =
+ Modifier.align(Alignment.TopEnd)
+ // Output switcher chips must each be limited to at most 40% of the maximum
+ // width of the card.
+ //
+ // This saves the maximum possible width of the card so it can be referred
+ // to by child custom layout code below.
+ //
+ // The assumption is that the row can be as wide as the entire card.
+ .layout { measurable, constraints ->
+ cardMaxWidth = constraints.maxWidth
+ val placeable = measurable.measure(constraints)
+
+ layout(placeable.measuredWidth, placeable.measuredHeight) {
+ placeable.place(0, 0)
+ }
+ },
) {
viewModel.outputSwitcherChips.fastForEach { chip ->
- OutputSwitcherChip(viewModel = chip, colorScheme = colorScheme)
+ OutputSwitcherChip(
+ viewModel = chip,
+ colorScheme = colorScheme,
+ modifier =
+ Modifier
+ // Each chip must be limited to 40% of the width of the card at
+ // most.
+ //
+ // The underlying assumption is that there'll never be more than one
+ // chip with text and one more icon-only chip. Only the one with
+ // text can ever end up being too wide.
+ .layout { measurable, constraints ->
+ val placeable =
+ measurable.measure(
+ constraints.copy(
+ maxWidth =
+ min(
+ (cardMaxWidth * 0.4f).fastRoundToInt(),
+ constraints.maxWidth,
+ )
+ )
+ )
+
+ layout(placeable.measuredWidth, placeable.measuredHeight) {
+ placeable.place(0, 0)
+ }
+ },
+ )
}
}
}
@@ -663,11 +713,20 @@ private fun ContentScope.Navigation(
if (isSeekBarVisible) {
// To allow the seek bar slider to fade in and out, it's tagged as an element.
Element(key = Media.Elements.SeekBarSlider, modifier = Modifier.weight(1f)) {
+ val sliderDragDelta = remember {
+ // Not a mutableStateOf - this is never accessed in composition and
+ // using an anonymous object avoids generics boxing of inline Offset.
+ object {
+ var value = Offset.Zero
+ }
+ }
Slider(
interactionSource = interactionSource,
value = viewModel.progress,
onValueChange = { progress -> viewModel.onScrubChange(progress) },
- onValueChangeFinished = { viewModel.onScrubFinished() },
+ onValueChangeFinished = {
+ viewModel.onScrubFinished(sliderDragDelta.value)
+ },
colors = colors,
thumb = {
SeekBarThumb(interactionSource = interactionSource, colors = colors)
@@ -681,9 +740,43 @@ private fun ContentScope.Navigation(
)
},
modifier =
- Modifier.fillMaxWidth().clearAndSetSemantics {
- contentDescription = viewModel.contentDescription
- },
+ Modifier.fillMaxWidth()
+ .clearAndSetSemantics {
+ contentDescription = viewModel.contentDescription
+ }
+ .pointerInput(Unit) {
+ // Track and report the drag delta to the view-model so it
+ // can
+ // decide whether to accept the next onValueChangeFinished
+ // or
+ // reject it if the drag was overly vertical.
+ awaitPointerEventScope {
+ var down: PointerInputChange? = null
+ while (true) {
+ val event =
+ awaitPointerEvent(PointerEventPass.Initial)
+ when (event.type) {
+ PointerEventType.Press -> {
+ // A new gesture has begun. Record the
+ // initial
+ // down input change.
+ down = event.changes.last()
+ }
+
+ PointerEventType.Move -> {
+ // The pointer has moved. If it's the same
+ // pointer as the latest down, calculate and
+ // report the drag delta.
+ val change = event.changes.last()
+ if (change.id == down?.id) {
+ sliderDragDelta.value =
+ change.position - down.position
+ }
+ }
+ }
+ }
+ }
+ },
)
}
}
@@ -979,6 +1072,8 @@ private fun OutputSwitcherChip(
text = viewModel.text,
style = MaterialTheme.typography.bodySmall,
color = colorScheme.onPrimary,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt
index ca7334341c87..a3689926e937 100644
--- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.remedia.ui.viewmodel
import androidx.annotation.FloatRange
+import androidx.compose.ui.geometry.Offset
/**
* Models UI state for the navigation component of the UI (potentially containing the seek bar and
@@ -58,7 +59,7 @@ sealed interface MediaNavigationViewModel {
* A callback to invoke once the user finishes "scrubbing" (e.g. stopped moving the thumb of
* the seek bar). The position/progress should be committed.
*/
- val onScrubFinished: () -> Unit,
+ val onScrubFinished: (delta: Offset) -> Unit,
/** Accessibility string to attach to the seekbar UI element. */
val contentDescription: String,
) : MediaNavigationViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt
index 15951165a19e..19b08fa212db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt
@@ -26,9 +26,9 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.ImageBitmap
import com.android.systemui.classifier.Classifier
-import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -42,6 +42,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.Locale
+import kotlin.math.abs
import kotlin.math.roundToLong
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.awaitCancellation
@@ -114,8 +115,11 @@ constructor(
isScrubbing = true
seekProgress = progress
},
- onScrubFinished = {
- if (!falsingSystem.isFalseTouch(Classifier.MEDIA_SEEKBAR)) {
+ onScrubFinished = { dragDelta ->
+ if (
+ dragDelta.isHorizontal() &&
+ !falsingSystem.isFalseTouch(Classifier.MEDIA_SEEKBAR)
+ ) {
interactor.seek(
sessionKey = session.key,
to = (seekProgress * session.durationMs).roundToLong(),
@@ -346,6 +350,14 @@ constructor(
.formatMeasures(*measures.toTypedArray())
}
+ /**
+ * Returns `true` if this [Offset] is the same or larger on the horizontal axis than the
+ * vertical axis.
+ */
+ private fun Offset.isHorizontal(): Boolean {
+ return abs(x) >= abs(y)
+ }
+
interface FalsingSystem {
fun runIfNotFalseTap(@FalsingManager.Penalty penalty: Int, block: () -> Unit)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index 69b967a68c3c..eb0947f2f844 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -113,6 +113,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastMap
+import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory
import com.android.compose.modifiers.height
import com.android.systemui.common.ui.compose.load
import com.android.systemui.qs.panels.shared.model.SizedTile
@@ -215,7 +216,9 @@ fun DefaultEditTileGrid(
containerColor = Color.Transparent,
topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) },
) { innerPadding ->
- CompositionLocalProvider(LocalOverscrollFactory provides null) {
+ CompositionLocalProvider(
+ LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
+ ) {
val scrollState = rememberScrollState()
AutoScrollGrid(listState, scrollState, innerPadding)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index f45971b57b65..2bacee12db8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -86,7 +86,7 @@ open class BlurUtils @Inject constructor(
*/
fun prepareBlur(viewRootImpl: ViewRootImpl?, radius: Int) {
if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid ||
- !supportsBlursOnWindows() || earlyWakeupEnabled
+ !shouldBlur(radius) || earlyWakeupEnabled
) {
return
}
@@ -113,7 +113,7 @@ open class BlurUtils @Inject constructor(
return
}
createTransaction().use {
- if (supportsBlursOnWindows()) {
+ if (shouldBlur(radius)) {
it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius)
if (!earlyWakeupEnabled && lastAppliedBlur == 0 && radius != 0) {
Trace.asyncTraceForTrackBegin(
@@ -142,6 +142,14 @@ open class BlurUtils @Inject constructor(
return SurfaceControl.Transaction()
}
+ private fun shouldBlur(radius: Int): Boolean {
+ return supportsBlursOnWindows() ||
+ ((Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) &&
+ supportsBlursOnWindowsBase() &&
+ lastAppliedBlur > 0 &&
+ radius == 0)
+ }
+
/**
* If this device can render blurs.
*
@@ -149,8 +157,11 @@ open class BlurUtils @Inject constructor(
* @return {@code true} when supported.
*/
open fun supportsBlursOnWindows(): Boolean {
+ return supportsBlursOnWindowsBase() && crossWindowBlurListeners.isCrossWindowBlurEnabled
+ }
+
+ private fun supportsBlursOnWindowsBase(): Boolean {
return CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx() &&
- crossWindowBlurListeners.isCrossWindowBlurEnabled() &&
!SystemProperties.getBoolean("persist.sysui.disableBlur", false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 2e83910a2a93..472dc823423e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -19,8 +19,6 @@ package com.android.systemui.statusbar
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.content.Context
-import android.content.res.Configuration
import android.os.SystemClock
import android.util.IndentingPrintWriter
import android.util.Log
@@ -42,16 +40,14 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.WallpaperController
import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
@@ -82,13 +78,11 @@ constructor(
private val wallpaperInteractor: WallpaperInteractor,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val dozeParameters: DozeParameters,
- @ShadeDisplayAware private val context: Context,
- private val splitShadeStateController: SplitShadeStateController,
+ private val shadeModeInteractor: ShadeModeInteractor,
private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
private val appZoomOutOptional: Optional<AppZoomOut>,
@Application private val applicationScope: CoroutineScope,
dumpManager: DumpManager,
- configurationController: ConfigurationController,
) : ShadeExpansionListener, Dumpable {
companion object {
private const val WAKE_UP_ANIMATION_ENABLED = true
@@ -110,7 +104,6 @@ constructor(
private var isOpen: Boolean = false
private var isBlurred: Boolean = false
private var listeners = mutableListOf<DepthListener>()
- private var inSplitShade: Boolean = false
private var prevTracking: Boolean = false
private var prevTimestamp: Long = -1
@@ -294,7 +287,7 @@ constructor(
private fun blurRadiusToZoomOut(blurRadius: Float): Float {
var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(blurRadius))
- if (inSplitShade) {
+ if (shadeModeInteractor.isSplitShade) {
zoomOut = 0f
}
@@ -432,14 +425,6 @@ constructor(
}
shadeAnimation.setStiffness(SpringForce.STIFFNESS_LOW)
shadeAnimation.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
- updateResources()
- configurationController.addCallback(
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateResources()
- }
- }
- )
applicationScope.launch {
wallpaperInteractor.wallpaperSupportsAmbientMode.collect { supported ->
wallpaperSupportsAmbientMode = supported
@@ -469,10 +454,6 @@ constructor(
}
}
- private fun updateResources() {
- inSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources)
- }
-
fun addListener(listener: DepthListener) {
listeners.add(listener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 2fe627020ebf..1a802d634894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -141,7 +141,7 @@ constructor(
// When we're promoting notifications automatically, the `when` time set on the
// notification will likely just be set to the current time, which would cause the chip
// to always show "now". We don't want early testers to get that experience since it's
- // not what will happen at launch, so just don't show any time.
+ // not what will happen at launch, so just don't show any time.onometerstate
return OngoingActivityChipModel.Active.IconOnly(
this.key,
icon,
@@ -194,14 +194,14 @@ constructor(
}
}
is PromotedNotificationContentModel.When.Chronometer -> {
- // TODO(b/364653005): Check isCountDown and support CountDown.
return OngoingActivityChipModel.Active.Timer(
this.key,
icon,
colors,
startTimeMs = this.promotedContent.time.elapsedRealtimeMillis,
- onClickListenerLegacy,
- clickBehavior,
+ isEventInFuture = this.promotedContent.time.isCountDown,
+ onClickListenerLegacy = onClickListenerLegacy,
+ clickBehavior = clickBehavior,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt
index 2032ec8af78c..1eb46d8cc3d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.ui.binder
+import android.annotation.ElapsedRealtimeLong
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
object ChipChronometerBinder {
@@ -25,9 +26,11 @@ object ChipChronometerBinder {
* @param startTimeMs the time this event started, relative to
* [com.android.systemui.util.time.SystemClock.elapsedRealtime]. See
* [android.widget.Chronometer.setBase].
+ * @param isCountDown see [android.widget.Chronometer.setCountDown].
*/
- fun bind(startTimeMs: Long, view: ChipChronometer) {
+ fun bind(@ElapsedRealtimeLong startTimeMs: Long, isCountDown: Boolean, view: ChipChronometer) {
view.base = startTimeMs
+ view.isCountDown = isCountDown
view.start()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 6f8552738d33..77e0dde3dec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -315,7 +315,11 @@ object OngoingActivityChipBinder {
chipShortTimeDeltaView.visibility = View.GONE
}
is OngoingActivityChipModel.Active.Timer -> {
- ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
+ ChipChronometerBinder.bind(
+ chipModel.startTimeMs,
+ chipModel.isEventInFuture,
+ chipTimeView,
+ )
chipTimeView.visibility = View.VISIBLE
chipTextView.visibility = View.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
index 55d753662a65..fa8d25623d67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
@@ -74,25 +74,31 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
val textMeasurer = rememberTextMeasurer()
when (viewModel) {
is OngoingActivityChipModel.Active.Timer -> {
- val timerState = rememberChronometerState(startTimeMillis = viewModel.startTimeMs)
- val text = timerState.currentTimeText
- Text(
- text = text,
- style = textStyle,
- color = textColor,
- softWrap = false,
- modifier =
- modifier
- .hideTextIfDoesNotFit(
- text = text,
- textStyle = textStyle,
- textMeasurer = textMeasurer,
- maxTextWidth = maxTextWidth,
- startPadding = startPadding,
- endPadding = endPadding,
- )
- .neverDecreaseWidth(density),
- )
+ val timerState =
+ rememberChronometerState(
+ eventTimeMillis = viewModel.startTimeMs,
+ isCountDown = viewModel.isEventInFuture,
+ timeSource = viewModel.timeSource,
+ )
+ timerState.currentTimeText?.let { text ->
+ Text(
+ text = text,
+ style = textStyle,
+ color = textColor,
+ softWrap = false,
+ modifier =
+ modifier
+ .hideTextIfDoesNotFit(
+ text = text,
+ textStyle = textStyle,
+ textMeasurer = textMeasurer,
+ maxTextWidth = maxTextWidth,
+ startPadding = startPadding,
+ endPadding = endPadding,
+ )
+ .neverDecreaseWidth(density),
+ )
+ }
}
is OngoingActivityChipModel.Active.Countdown -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
index 7080c3402b08..407849b9fae0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
@@ -36,18 +36,18 @@ fun OngoingActivityChips(
iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
modifier: Modifier = Modifier,
) {
- Row(
- modifier =
- modifier
- .fillMaxHeight()
- .padding(start = dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement =
- Arrangement.spacedBy(dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
- ) {
- chips.active
- .filter { !it.isHidden }
- .forEach {
+ val shownChips = chips.active.filter { !it.isHidden }
+ if (shownChips.isNotEmpty()) {
+ Row(
+ modifier =
+ modifier
+ .fillMaxHeight()
+ .padding(start = dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement =
+ Arrangement.spacedBy(dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
+ ) {
+ shownChips.forEach {
key(it.key) {
OngoingActivityChip(
model = it,
@@ -56,5 +56,6 @@ fun OngoingActivityChips(
)
}
}
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 8e470742f174..d37a46e58882 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -18,12 +18,14 @@ package com.android.systemui.statusbar.chips.ui.model
import android.annotation.CurrentTimeMillisLong
import android.annotation.ElapsedRealtimeLong
+import android.os.SystemClock
import android.view.View
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ui.viewmodel.TimeSource
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
/** Model representing the display of an ongoing activity as a chip in the status bar. */
@@ -105,6 +107,19 @@ sealed class OngoingActivityChipModel {
* [android.widget.Chronometer.setBase].
*/
@ElapsedRealtimeLong val startTimeMs: Long,
+
+ /**
+ * The [TimeSource] that should be used to track the current time for this timer. Should
+ * be compatible with [startTimeMs].
+ */
+ val timeSource: TimeSource = TimeSource { SystemClock.elapsedRealtime() },
+
+ /**
+ * True if this chip represents an event starting in the future and false if this chip
+ * represents an event that has already started. If true, [startTimeMs] should be in the
+ * future. Otherwise, [startTimeMs] should be in the past.
+ */
+ val isEventInFuture: Boolean = false,
override val onClickListenerLegacy: View.OnClickListener?,
override val clickBehavior: ClickBehavior,
override val isHidden: Boolean = false,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt
index 62789782d0a9..0fc7f82f785a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.statusbar.chips.ui.viewmodel
-import android.os.SystemClock
+import android.annotation.ElapsedRealtimeLong
import android.text.format.DateUtils.formatElapsedTime
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
+import kotlin.math.absoluteValue
import kotlinx.coroutines.delay
/** Platform-optimized interface for getting current time */
@@ -34,18 +35,50 @@ fun interface TimeSource {
fun getCurrentTime(): Long
}
-/** Holds and manages the state for a Chronometer */
-class ChronometerState(private val timeSource: TimeSource, private val startTimeMillis: Long) {
- private var currentTimeMillis by mutableLongStateOf(0L)
+/**
+ * Holds and manages the state for a Chronometer, which shows a timer in a format like "MM:SS" or
+ * "H:MM:SS".
+ *
+ * If [isEventInFuture] is false, then this Chronometer is counting up from an event that started in
+ * the past, like a phone call that was answered. [eventTimeMillis] represents the time the event
+ * started and the timer will tick up: 04:00, 04:01, ... No timer is shown if [eventTimeMillis] is
+ * in the future and [isEventInFuture] is false.
+ *
+ * If [isEventInFuture] is true, then this Chronometer is counting down to an event that will occur
+ * in the future, like a future meeting. [eventTimeMillis] represents the time the event will occur
+ * and the timer will tick down: 04:00, 03:59, ... No timer is shown if [eventTimeMillis] is in the
+ * past and [isEventInFuture] is true.
+ */
+class ChronometerState(
+ private val timeSource: TimeSource,
+ @ElapsedRealtimeLong private val eventTimeMillis: Long,
+ private val isEventInFuture: Boolean,
+) {
+ private var currentTimeMillis by mutableLongStateOf(timeSource.getCurrentTime())
private val elapsedTimeMillis: Long
- get() = maxOf(0L, currentTimeMillis - startTimeMillis)
+ get() =
+ if (isEventInFuture) {
+ eventTimeMillis - currentTimeMillis
+ } else {
+ currentTimeMillis - eventTimeMillis
+ }
- val currentTimeText: String by derivedStateOf { formatElapsedTime(elapsedTimeMillis / 1000) }
+ /**
+ * The current timer string in a format like "MM:SS" or "H:MM:SS", or null if we shouldn't show
+ * the timer string.
+ */
+ val currentTimeText: String? by derivedStateOf {
+ if (elapsedTimeMillis < 0) {
+ null
+ } else {
+ formatElapsedTime(elapsedTimeMillis / 1000)
+ }
+ }
suspend fun run() {
while (true) {
currentTimeMillis = timeSource.getCurrentTime()
- val delaySkewMillis = (currentTimeMillis - startTimeMillis) % 1000L
+ val delaySkewMillis = (eventTimeMillis - currentTimeMillis).absoluteValue % 1000L
delay(1000L - delaySkewMillis)
}
}
@@ -54,13 +87,16 @@ class ChronometerState(private val timeSource: TimeSource, private val startTime
/** Remember and manage the ChronometerState */
@Composable
fun rememberChronometerState(
- startTimeMillis: Long,
- timeSource: TimeSource = remember { TimeSource { SystemClock.elapsedRealtime() } },
+ eventTimeMillis: Long,
+ isCountDown: Boolean,
+ timeSource: TimeSource,
): ChronometerState {
val state =
- remember(timeSource, startTimeMillis) { ChronometerState(timeSource, startTimeMillis) }
+ remember(timeSource, eventTimeMillis, isCountDown) {
+ ChronometerState(timeSource, eventTimeMillis, isCountDown)
+ }
val lifecycleOwner = LocalLifecycleOwner.current
- LaunchedEffect(lifecycleOwner, timeSource, startTimeMillis) {
+ LaunchedEffect(lifecycleOwner, timeSource, eventTimeMillis) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { state.run() }
}
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 238ba8d9f490..780e8f47a7fe 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
@@ -289,6 +289,9 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
mIdToBundleEntry.clear();
for (String id: mNotifBundler.getBundleIds()) {
+ if (BundleCoordinator.debugBundleUi) {
+ Log.i(TAG, "create BundleEntry with id: " + id);
+ }
mIdToBundleEntry.put(id, new BundleEntry(id));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
index 8833ff1ce20c..4478d0e97920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
@@ -98,9 +98,14 @@ class BundleCoordinator @Inject constructor(
object : NotifBundler("NotifBundler") {
// Use list instead of set to keep fixed order
- override val bundleIds: List<String> = SYSTEM_RESERVED_IDS
+ override val bundleIds: List<String> =
+ if (debugBundleUi) SYSTEM_RESERVED_IDS + "notify"
+ else SYSTEM_RESERVED_IDS
override fun getBundleIdOrNull(entry: NotificationEntry?): String? {
+ if (debugBundleUi && entry?.key?.contains("notify") == true) {
+ return "notify"
+ }
return entry?.representativeEntry?.channel?.id?.takeIf { it in this.bundleIds }
}
}
@@ -110,4 +115,9 @@ class BundleCoordinator @Inject constructor(
pipeline.setNotifBundler(bundler)
}
}
+
+ companion object {
+ @kotlin.jvm.JvmField
+ var debugBundleUi: Boolean = true
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index bdbdc53c4b1c..27765635edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -39,9 +39,9 @@ import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.PipelineEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
@@ -113,6 +113,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
@VisibleForTesting
protected static final long ALLOW_SECTION_CHANGE_TIMEOUT = 500;
+ private final boolean mCheckLockScreenTransitionEnabled = Flags.checkLockscreenGoneTransition();
+
@Inject
public VisualStabilityCoordinator(
@Background DelayableExecutor delayableExecutor,
@@ -182,7 +184,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
this::onTrackingHeadsUpModeChanged);
}
- if (Flags.checkLockscreenGoneTransition()) {
+ if (mCheckLockScreenTransitionEnabled) {
if (SceneContainerFlag.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone), null),
@@ -437,7 +439,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
boolean wasReorderingAllowed = mReorderingAllowed;
// No need to run notification pipeline when the lockscreen is in fading animation.
mPipelineRunAllowed = !(isPanelCollapsingOrLaunchingActivity()
- || (Flags.checkLockscreenGoneTransition() && mLockscreenInGoneTransition));
+ || (mCheckLockScreenTransitionEnabled && mLockscreenInGoneTransition));
mReorderingAllowed = isReorderingAllowed();
if (wasPipelineRunAllowed != mPipelineRunAllowed
|| wasReorderingAllowed != mReorderingAllowed) {
@@ -566,7 +568,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
pw.println("pipelineRunAllowed: " + mPipelineRunAllowed);
pw.println(" notifPanelCollapsing: " + mNotifPanelCollapsing);
pw.println(" launchingNotifActivity: " + mNotifPanelLaunchingActivity);
- if (Flags.checkLockscreenGoneTransition()) {
+ if (mCheckLockScreenTransitionEnabled) {
pw.println(" lockscreenInGoneTransition: " + mLockscreenInGoneTransition);
}
pw.println("reorderingAllowed: " + mReorderingAllowed);
@@ -627,7 +629,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
}
private void onLockscreenInGoneTransitionChanged(boolean inGoneTransition) {
- if (!Flags.checkLockscreenGoneTransition()) {
+ if (!mCheckLockScreenTransitionEnabled) {
return;
}
if (inGoneTransition == mLockscreenInGoneTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index 2aafe8c81381..d35c3b617246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -20,6 +20,7 @@ import android.app.Notification
import android.app.Notification.BigPictureStyle
import android.app.Notification.BigTextStyle
import android.app.Notification.CallStyle
+import android.app.Notification.EXTRA_BIG_TEXT
import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN
import android.app.Notification.EXTRA_PROGRESS
import android.app.Notification.EXTRA_PROGRESS_INDETERMINATE
@@ -27,8 +28,10 @@ import android.app.Notification.EXTRA_PROGRESS_MAX
import android.app.Notification.EXTRA_SUB_TEXT
import android.app.Notification.EXTRA_TEXT
import android.app.Notification.EXTRA_TITLE
+import android.app.Notification.EXTRA_TITLE_BIG
import android.app.Notification.EXTRA_VERIFICATION_ICON
import android.app.Notification.EXTRA_VERIFICATION_TEXT
+import android.app.Notification.InboxStyle
import android.app.Notification.ProgressStyle
import android.content.Context
import android.graphics.drawable.Icon
@@ -105,8 +108,8 @@ constructor(
contentBuilder.shortCriticalText = notification.shortCriticalText()
contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
contentBuilder.profileBadgeResId = null // TODO
- contentBuilder.title = notification.title()
- contentBuilder.text = notification.text()
+ contentBuilder.title = notification.resolveTitle(recoveredBuilder.style)
+ contentBuilder.text = notification.resolveText(recoveredBuilder.style)
contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider)
contentBuilder.oldProgress = notification.oldProgress()
@@ -127,8 +130,39 @@ constructor(
private fun Notification.title(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE)
+ private fun Notification.bigTitle(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE_BIG)
+
+ private fun Notification.Style.bigTitleOverridesTitle(): Boolean {
+ return when (this) {
+ is BigTextStyle,
+ is BigPictureStyle,
+ is InboxStyle -> true
+ else -> false
+ }
+ }
+
+ private fun Notification.resolveTitle(style: Notification.Style?): CharSequence? {
+ return if (style?.bigTitleOverridesTitle() == true) {
+ bigTitle()
+ } else {
+ null
+ } ?: title()
+ }
+
private fun Notification.text(): CharSequence? = extras?.getCharSequence(EXTRA_TEXT)
+ private fun Notification.bigText(): CharSequence? = extras?.getCharSequence(EXTRA_BIG_TEXT)
+
+ private fun Notification.Style.bigTextOverridesText(): Boolean = this is BigTextStyle
+
+ private fun Notification.resolveText(style: Notification.Style?): CharSequence? {
+ return if (style?.bigTextOverridesText() == true) {
+ bigText()
+ } else {
+ null
+ } ?: text()
+ }
+
private fun Notification.subText(): String? = extras?.getString(EXTRA_SUB_TEXT)
private fun Notification.shortCriticalText(): String? {
@@ -233,13 +267,13 @@ constructor(
private fun BigPictureStyle.extractContent(
contentBuilder: PromotedNotificationContentModel.Builder
) {
- // TODO?
+ // Big title is handled in resolveTitle, and big picture is unsupported.
}
private fun BigTextStyle.extractContent(
contentBuilder: PromotedNotificationContentModel.Builder
) {
- // TODO?
+ // Big title and big text are handled in resolveTitle and resolveText.
}
private fun CallStyle.extractContent(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt
new file mode 100644
index 000000000000..14295357c54f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** A class which can receive both a demotion signal and a single handler of that signal */
+@SysUISingleton
+class PackageDemotionInteractor @Inject constructor() {
+ private var demotionSignalHandler: ((packageName: String, uid: Int) -> Unit)? = null
+
+ /**
+ * called after sending a the demotion signal to
+ * [android.app.INotificationManager.setCanBePromoted]
+ */
+ fun onPackageDemoted(packageName: String, uid: Int) {
+ demotionSignalHandler?.invoke(packageName, uid)
+ }
+
+ /** sets the [handler] that will be called when [onPackageDemoted] is called. */
+ fun setOnPackageDemotionHandler(handler: (packageName: String, uid: Int) -> Unit) {
+ demotionSignalHandler = handler
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 292f74a65554..f36a0cf51b97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.Flags.notificationColorUpdateLogger;
import static com.android.systemui.Flags.physicalNotificationMovement;
+import static java.lang.Math.abs;
+
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Configuration;
@@ -29,6 +31,7 @@ import android.util.FloatProperty;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;
@@ -110,14 +113,27 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
protected SpringAnimation mMagneticAnimator = new SpringAnimation(
this /* object */, DynamicAnimation.TRANSLATION_X);
+ private int mTouchSlop;
+
protected MagneticRowListener mMagneticRowListener = new MagneticRowListener() {
@Override
- public void setMagneticTranslation(float translation) {
- if (mMagneticAnimator.isRunning()) {
- mMagneticAnimator.animateToFinalPosition(translation);
- } else {
+ public void setMagneticTranslation(float translation, boolean trackEagerly) {
+ if (!mMagneticAnimator.isRunning()) {
setTranslation(translation);
+ return;
+ }
+
+ if (trackEagerly) {
+ float delta = abs(getTranslation() - translation);
+ if (delta > mTouchSlop) {
+ mMagneticAnimator.animateToFinalPosition(translation);
+ } else {
+ mMagneticAnimator.cancel();
+ setTranslation(translation);
+ }
+ } else {
+ mMagneticAnimator.animateToFinalPosition(translation);
}
}
@@ -183,6 +199,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
private void initDimens() {
mContentShift = getResources().getDimensionPixelSize(
R.dimen.shelf_transform_content_shift);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@Override
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 9a75253295d5..55a564944f00 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
@@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
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;
@@ -100,6 +101,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
private final AccessibilityManager mAccessibilityManager;
private final HighPriorityProvider mHighPriorityProvider;
private final ChannelEditorDialogController mChannelEditorDialogController;
+ private final PackageDemotionInteractor mPackageDemotionInteractor;
private final OnUserInteractionCallback mOnUserInteractionCallback;
// Dependencies:
@@ -155,6 +157,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
LauncherApps launcherApps,
ShortcutManager shortcutManager,
ChannelEditorDialogController channelEditorDialogController,
+ PackageDemotionInteractor packageDemotionInteractor,
UserContextProvider contextTracker,
AssistantFeedbackController assistantFeedbackController,
Optional<BubblesManager> bubblesManagerOptional,
@@ -184,6 +187,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
mShortcutManager = shortcutManager;
mContextTracker = contextTracker;
mChannelEditorDialogController = channelEditorDialogController;
+ mPackageDemotionInteractor = packageDemotionInteractor;
mAssistantFeedbackController = assistantFeedbackController;
mBubblesManagerOptional = bubblesManagerOptional;
mUiEventLogger = uiEventLogger;
@@ -429,6 +433,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
mIconStyleProvider,
mOnUserInteractionCallback,
mChannelEditorDialogController,
+ mPackageDemotionInteractor,
packageName,
row.getEntry().getChannel(),
row.getEntry(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 661122510c6c..904911fcf545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -74,6 +74,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
@@ -193,6 +194,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
NotificationIconStyleProvider iconStyleProvider,
OnUserInteractionCallback onUserInteractionCallback,
ChannelEditorDialogController channelEditorDialogController,
+ PackageDemotionInteractor packageDemotionInteractor,
String pkg,
NotificationChannel notificationChannel,
NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index e89a76fd5a69..286f07b7413d 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
@@ -32,7 +32,6 @@ import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
@@ -358,7 +357,9 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
final float dismissThreshold = getDismissThreshold();
final boolean snappingToDismiss = delta < -dismissThreshold || delta > dismissThreshold;
if (mSnappingToDismiss != snappingToDismiss) {
- getMenuView().performHapticFeedback(CLOCK_TICK);
+ if (!Flags.magneticNotificationSwipes()) {
+ getMenuView().performHapticFeedback(CLOCK_TICK);
+ }
}
mSnappingToDismiss = snappingToDismiss;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
index 6ff711deeb01..39ffdf29b57c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
@@ -31,6 +31,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
@@ -42,6 +43,7 @@ import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyl
public class PromotedNotificationInfo extends NotificationInfo {
private static final String TAG = "PromotedNotifInfoGuts";
private INotificationManager mNotificationManager;
+ private PackageDemotionInteractor mPackageDemotionInteractor;
private NotificationGuts mGutsContainer;
public PromotedNotificationInfo(Context context, AttributeSet attrs) {
@@ -56,6 +58,7 @@ public class PromotedNotificationInfo extends NotificationInfo {
NotificationIconStyleProvider iconStyleProvider,
OnUserInteractionCallback onUserInteractionCallback,
ChannelEditorDialogController channelEditorDialogController,
+ PackageDemotionInteractor packageDemotionInteractor,
String pkg,
NotificationChannel notificationChannel,
NotificationEntry entry,
@@ -69,12 +72,14 @@ public class PromotedNotificationInfo extends NotificationInfo {
AssistantFeedbackController assistantFeedbackController,
MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException {
super.bindNotification(pm, iNotificationManager, appIconProvider, iconStyleProvider,
- onUserInteractionCallback, channelEditorDialogController, pkg, notificationChannel,
+ onUserInteractionCallback, channelEditorDialogController, packageDemotionInteractor,
+ pkg, notificationChannel,
entry, onSettingsClick, onAppSettingsClick, feedbackClickListener, uiEventLogger,
isDeviceProvisioned, isNonblockable, wasShownHighPriority,
assistantFeedbackController, metricsLogger, onCloseClick);
mNotificationManager = iNotificationManager;
+ mPackageDemotionInteractor = packageDemotionInteractor;
bindDemote(entry.getSbn(), pkg);
}
@@ -94,8 +99,8 @@ public class PromotedNotificationInfo extends NotificationInfo {
private OnClickListener getDemoteClickListener(StatusBarNotification sbn, String packageName) {
return ((View v) -> {
try {
- // TODO(b/391661009): Signal AutomaticPromotionCoordinator here
mNotificationManager.setCanBePromoted(packageName, sbn.getUid(), false, true);
+ mPackageDemotionInteractor.onPackageDemoted(packageName, sbn.getUid());
mGutsContainer.closeControls(v, true);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't revoke live update permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
index aa6951715755..48cff7497e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
@@ -33,12 +33,12 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
interface MagneticNotificationRowManager {
/**
- * Set the swipe threshold in pixels. After crossing the threshold, the magnetic target detaches
- * and the magnetic neighbors snap back.
+ * Notifies a change in the device density. The density can be used to compute the values of
+ * thresholds in pixels.
*
- * @param[threshold] Swipe threshold in pixels.
+ * @param[density] The device density.
*/
- fun setSwipeThresholdPx(thresholdPx: Float)
+ fun onDensityChange(density: Float)
/**
* Set the magnetic and roundable targets of a magnetic swipe interaction.
@@ -87,6 +87,9 @@ interface MagneticNotificationRowManager {
*/
fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null)
+ /** Determine if the given [ExpandableNotificationRow] has been magnetically detached. */
+ fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean
+
/* Reset any roundness that magnetic targets may have */
fun resetRoundness()
@@ -104,12 +107,15 @@ interface MagneticNotificationRowManager {
/** Detaching threshold in dp */
const val MAGNETIC_DETACH_THRESHOLD_DP = 56
+ /** Re-attaching threshold in dp */
+ const val MAGNETIC_ATTACH_THRESHOLD_DP = 40
+
/* An empty implementation of a manager */
@JvmStatic
val Empty: MagneticNotificationRowManager
get() =
object : MagneticNotificationRowManager {
- override fun setSwipeThresholdPx(thresholdPx: Float) {}
+ override fun onDensityChange(density: Float) {}
override fun setMagneticAndRoundableTargets(
swipingRow: ExpandableNotificationRow,
@@ -127,6 +133,10 @@ interface MagneticNotificationRowManager {
velocity: Float?,
) {}
+ override fun isMagneticRowSwipeDetached(
+ row: ExpandableNotificationRow
+ ): Boolean = false
+
override fun resetRoundness() {}
override fun reset() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
index 9bd5a5bd903f..3f05cef6a0c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
@@ -47,6 +47,7 @@ constructor(
private set
private var magneticDetachThreshold = Float.POSITIVE_INFINITY
+ private var magneticAttachThreshold = 0f
// Has the roundable target been set for the magnetic view that is being swiped.
val isSwipedViewRoundableSet: Boolean
@@ -57,13 +58,18 @@ constructor(
SpringForce().setStiffness(DETACH_STIFFNESS).setDampingRatio(DETACH_DAMPING_RATIO)
private val snapForce =
SpringForce().setStiffness(SNAP_BACK_STIFFNESS).setDampingRatio(SNAP_BACK_DAMPING_RATIO)
+ private val attachForce =
+ SpringForce().setStiffness(ATTACH_STIFFNESS).setDampingRatio(ATTACH_DAMPING_RATIO)
// Multiplier applied to the translation of a row while swiped
val swipedRowMultiplier =
MAGNETIC_TRANSLATION_MULTIPLIERS[MAGNETIC_TRANSLATION_MULTIPLIERS.size / 2]
- override fun setSwipeThresholdPx(thresholdPx: Float) {
- magneticDetachThreshold = thresholdPx
+ override fun onDensityChange(density: Float) {
+ magneticDetachThreshold =
+ density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+ magneticAttachThreshold =
+ density * MagneticNotificationRowManager.MAGNETIC_ATTACH_THRESHOLD_DP
}
override fun setMagneticAndRoundableTargets(
@@ -139,8 +145,7 @@ constructor(
}
}
State.DETACHED -> {
- val swiped = currentMagneticListeners.swipedListener()
- swiped?.setMagneticTranslation(translation)
+ translateDetachedRow(translation)
}
}
return true
@@ -232,6 +237,41 @@ constructor(
)
}
+ private fun translateDetachedRow(translation: Float) {
+ val targetTranslation = swipedRowMultiplier * translation
+ val crossedThreshold = abs(targetTranslation) <= magneticAttachThreshold
+ if (crossedThreshold) {
+ attachNeighbors(translation)
+ updateRoundness(translation)
+ currentMagneticListeners.swipedListener()?.let { attach(it, translation) }
+ currentState = State.PULLING
+ } else {
+ val swiped = currentMagneticListeners.swipedListener()
+ swiped?.setMagneticTranslation(translation, trackEagerly = false)
+ }
+ }
+
+ private fun attachNeighbors(translation: Float) {
+ currentMagneticListeners.forEachIndexed { i, target ->
+ target?.let {
+ if (i != currentMagneticListeners.size / 2) {
+ val multiplier = MAGNETIC_TRANSLATION_MULTIPLIERS[i]
+ target.cancelMagneticAnimations()
+ target.triggerMagneticForce(
+ endTranslation = translation * multiplier,
+ attachForce,
+ )
+ }
+ }
+ }
+ }
+
+ private fun attach(listener: MagneticRowListener, toPosition: Float) {
+ listener.cancelMagneticAnimations()
+ listener.triggerMagneticForce(toPosition, attachForce)
+ msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
+ }
+
override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) {
if (row.isSwipedTarget()) {
when (currentState) {
@@ -254,6 +294,9 @@ constructor(
}
}
+ override fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean =
+ row.isSwipedTarget() && currentState == State.DETACHED
+
override fun resetRoundness() = notificationRoundnessManager.clear()
override fun reset() {
@@ -300,6 +343,8 @@ constructor(
private const val DETACH_DAMPING_RATIO = 0.95f
private const val SNAP_BACK_STIFFNESS = 550f
private const val SNAP_BACK_DAMPING_RATIO = 0.6f
+ private const val ATTACH_STIFFNESS = 800f
+ private const val ATTACH_DAMPING_RATIO = 0.95f
// Maximum value of corner roundness that gets applied during the pre-detach dragging
private const val MAX_PRE_DETACH_ROUNDNESS = 0.8f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
index 5959ef1e093b..c3b9024ad9b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
@@ -21,8 +21,21 @@ import androidx.dynamicanimation.animation.SpringForce
/** A listener that responds to magnetic forces applied to an [ExpandableNotificationRow] */
interface MagneticRowListener {
- /** Set a translation due to a magnetic attachment. */
- fun setMagneticTranslation(translation: Float)
+ /**
+ * Set a translation due to a magnetic attachment.
+ *
+ * The request to set a View's translation may occur while a magnetic animation is running. In
+ * such a case, the [trackEagerly] argument will determine if we decide to eagerly track the
+ * incoming translation or not. If true, the ongoing animation will update its target position
+ * and continue to animate only if the incoming translation produces a delta higher than a touch
+ * slop threshold. Otherwise, the animation will be cancelled and the View translation will be
+ * set directly. If [trackEagerly] is false, we respect the animation and only update the
+ * animated target.
+ *
+ * @param[translation] Incoming gesture translation.
+ * @param[trackEagerly] Whether we eagerly track the incoming translation directly or not.
+ */
+ fun setMagneticTranslation(translation: Float, trackEagerly: Boolean = true)
/**
* Trigger the magnetic behavior when the row detaches or snaps back from its magnetic
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 e6bb1b9f0273..a5f711050c46 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
@@ -64,6 +64,7 @@ import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
+import android.util.Pair;
import android.view.DisplayCutout;
import android.view.InputDevice;
import android.view.LayoutInflater;
@@ -140,6 +141,7 @@ import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScr
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxy;
import com.android.systemui.util.Assert;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.DumpUtilsKt;
@@ -3852,8 +3854,6 @@ public class NotificationStackScrollLayout
// existing overScroll, we have to scroll the view
customOverScrollBy((int) scrollAmount, getOwnScrollY(),
range, getHeight() / 2);
- // If we're scrolling, leavebehinds should be dismissed
- mController.checkSnoozeLeavebehind();
}
}
break;
@@ -6002,7 +6002,6 @@ public class NotificationStackScrollLayout
* LockscreenShadeTransitionController resets fraction to 0
* where it remains until the next lockscreen-to-shade transition.
*/
- @Override
public void setFractionToShade(float fraction) {
mAmbientState.setFractionToShade(fraction);
updateContentHeight(); // Recompute stack height with different section gap.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index f7f8acf5fda9..2d0a1c069c5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -486,15 +486,22 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
@Override
+ public boolean isMagneticViewDetached(View view) {
+ if (view instanceof ExpandableNotificationRow row) {
+ return mMagneticNotificationRowManager.isMagneticRowSwipeDetached(row);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
public float getTotalTranslationLength(View animView) {
return mView.getTotalTranslationLength(animView);
}
@Override
public void onDensityScaleChange(float density) {
- mMagneticNotificationRowManager.setSwipeThresholdPx(
- density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
- );
+ mMagneticNotificationRowManager.onDensityChange(density);
}
@Override
@@ -1735,7 +1742,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
* they remain until the next lockscreen-to-shade transition.
*/
public void setTransitionToFullShadeAmount(float fraction) {
- SceneContainerFlag.assertInLegacyMode();
mView.setFractionToShade(fraction);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index c5a846e1da05..33b94783b24a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -33,6 +33,7 @@ import android.view.ViewConfiguration;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.Flags;
import com.android.systemui.SwipeHelper;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -191,6 +192,11 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
@Override
public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
float translation) {
+ if (Flags.magneticNotificationSwipes()
+ && (mCallback.isMagneticViewDetached(animView) || swipedFastEnough())) {
+ dismiss(animView, velocity);
+ return true;
+ }
NotificationMenuRowPlugin menuRow = getCurrentMenuRow();
if (menuRow != null) {
menuRow.onTouchEnd();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index ac89f3a63fcd..9c855e9cd9b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -107,9 +107,6 @@ interface NotificationScrollView {
/** sets the current expand fraction */
fun setExpandFraction(expandFraction: Float)
- /** Sets the fraction of the LockScreen -> Shade transition. */
- fun setFractionToShade(fraction: Float)
-
/** sets the current QS expand fraction */
fun setQsExpandFraction(expandFraction: Float)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 40739b386d20..653344ae9203 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -95,11 +95,6 @@ constructor(
view.setExpandFraction(it.coerceIn(0f, 1f))
}
}
- launch {
- viewModel.lockScreenToShadeTransitionProgress.collectTraced {
- view.setFractionToShade(it.coerceIn(0f, 1f))
- }
- }
launch { viewModel.qsExpandFraction.collectTraced { view.setQsExpandFraction(it) } }
launch { viewModel.blurRadius(maxBlurRadius).collect(view::setBlurRadius) }
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 940b2e541758..c1aa5f12aa99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -207,44 +207,6 @@ constructor(
val qsExpandFraction: Flow<Float> =
shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
- /**
- * Fraction of the LockScreen -> Shade transition. 0..1 while the transition in progress, and
- * snaps back to 0 when it is Idle.
- */
- val lockScreenToShadeTransitionProgress: Flow<Float> =
- combine(
- shadeInteractor.shadeExpansion,
- shadeModeInteractor.shadeMode,
- sceneInteractor.transitionState,
- ) { shadeExpansion, _, transitionState ->
- when (transitionState) {
- is Idle -> 0f
- is ChangeScene ->
- if (
- transitionState.isTransitioning(
- from = Scenes.Lockscreen,
- to = Scenes.Shade,
- )
- ) {
- shadeExpansion
- } else {
- 0f
- }
-
- is Transition.OverlayTransition ->
- if (
- transitionState.currentScene == Scenes.Lockscreen &&
- transitionState.isTransitioning(to = Overlays.NotificationsShade)
- ) {
- shadeExpansion
- } else {
- 0f
- }
- }
- }
- .distinctUntilChanged()
- .dumpWhileCollecting("lockScreenToShadeTransitionProgress")
-
val isOccluded: Flow<Boolean> =
bouncerInteractor.bouncerExpansion
.map { it == 1f }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index c91ea9a50028..ba666512af5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -255,7 +255,7 @@ fun StatusBarRoot(
expandedMatchesParentHeight = true
showsOnlyActiveMedia = true
falsingProtectionNeeded = false
- disablePagination = true
+ disableScrolling = true
init(MediaHierarchyManager.LOCATION_STATUS_BAR_POPUP)
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9f60fe212567..48d7747d2dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -28,6 +28,7 @@ import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_DYNAMIC_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_BOTH;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_INDEX;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
@@ -106,6 +107,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
@@ -507,18 +509,52 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
+ // Condition only applies when booting to Setup Wizard.
+ // We should check if the device has specific hardware color styles, load the relative
+ // color palette and more also save the setting in our shared setting with ThemePicker.
WallpaperColors systemColor;
if (hardwareColorStyles() && !mDeviceProvisionedController.isCurrentUserSetup()) {
- Pair<Integer, Color> defaultSettings = getThemeSettingsDefaults();
- mThemeStyle = defaultSettings.first;
- Color seedColor = defaultSettings.second;
+ HardwareDefaultSetting defaultSettings = getThemeSettingsDefaults();
+ mThemeStyle = defaultSettings.style;
// we only use the first color anyway, so we can pass only the single color we have
systemColor = new WallpaperColors(
- /*primaryColor*/ seedColor,
- /*secondaryColor*/ seedColor,
- /*tertiaryColor*/ seedColor
+ /*primaryColor*/ defaultSettings.seedColor,
+ /*secondaryColor*/ defaultSettings.seedColor,
+ /*tertiaryColor*/ defaultSettings.seedColor
+ );
+
+ /* We update the json in THEME_CUSTOMIZATION_OVERLAY_PACKAGES to reflect the preset. */
+ final int currentUser = mUserTracker.getUserId();
+ final String overlayPackageJson = Objects.requireNonNullElse(
+ mSecureSettings.getStringForUser(
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ currentUser),
+ "{}"
);
+
+ try {
+ JSONObject object = new JSONObject(overlayPackageJson);
+ String seedColorStr = Integer.toHexString(defaultSettings.seedColor.toArgb());
+ object.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColorStr);
+ object.put(OVERLAY_CATEGORY_ACCENT_COLOR, seedColorStr);
+ object.put(OVERLAY_COLOR_SOURCE, defaultSettings.colorSource);
+ object.put(OVERLAY_CATEGORY_THEME_STYLE, Style.toString(mThemeStyle));
+
+ mSecureSettings.putStringForUser(
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, object.toString(),
+ UserHandle.USER_CURRENT);
+
+ Log.d(TAG, "Hardware Color Defaults loaded: " + object.toString());
+
+ } catch (JSONException e) {
+ Log.d(TAG, "Failed to store hardware color defaults in "
+ + "THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
+ }
+
+ // now we have to update
+
} else {
systemColor = mWallpaperManager.getWallpaperColors(
getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
@@ -863,7 +899,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
try {
JSONObject object = new JSONObject(overlayPackageJson);
style = Style.valueOf(
- object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE));
+ object.getString(OVERLAY_CATEGORY_THEME_STYLE));
if (!validStyles.contains(style)) {
style = Style.TONAL_SPOT;
}
@@ -899,26 +935,29 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
}
String deviceColorPropertyValue = mSystemPropertiesHelper.get(deviceColorProperty);
- Pair<Integer, String> selectedTheme = themeMap.get(deviceColorPropertyValue);
- if (selectedTheme == null) {
+ Pair<Integer, String> styleAndSource = themeMap.get(deviceColorPropertyValue);
+ if (styleAndSource == null) {
Log.d(TAG, "Sysprop `" + deviceColorProperty + "` of value '" + deviceColorPropertyValue
+ "' not found in theming_defaults: " + Arrays.toString(themeData));
- selectedTheme = fallbackTheme;
+ styleAndSource = fallbackTheme;
}
- return selectedTheme;
+ return styleAndSource;
+ }
+
+ record HardwareDefaultSetting(Color seedColor, @Style.Type int style, String colorSource) {
}
@VisibleForTesting
- protected Pair<Integer, Color> getThemeSettingsDefaults() {
+ protected HardwareDefaultSetting getThemeSettingsDefaults() {
- Pair<Integer, String> selectedTheme = getHardwareColorSetting();
+ Pair<Integer, String> styleAndSource = getHardwareColorSetting();
// Last fallback color
Color defaultSeedColor = Color.valueOf(GOOGLE_BLUE);
// defaultColor will come from wallpaper or be parsed from a string
- boolean isWallpaper = selectedTheme.second.equals(COLOR_SOURCE_HOME);
+ boolean isWallpaper = styleAndSource.second.equals(COLOR_SOURCE_HOME);
if (isWallpaper) {
WallpaperColors wallpaperColors = mWallpaperManager.getWallpaperColors(
@@ -932,16 +971,17 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
defaultSeedColor.toArgb()));
} else {
try {
- defaultSeedColor = Color.valueOf(Color.parseColor(selectedTheme.second));
+ defaultSeedColor = Color.valueOf(Color.parseColor(styleAndSource.second));
Log.d(TAG, "Default seed color read from resource: " + Integer.toHexString(
defaultSeedColor.toArgb()));
} catch (IllegalArgumentException e) {
- Log.e(TAG, "Error parsing color: " + selectedTheme.second, e);
+ Log.e(TAG, "Error parsing color: " + styleAndSource.second, e);
// defaultSeedColor remains unchanged in this case
}
}
- return new Pair<>(selectedTheme.first, defaultSeedColor);
+ return new HardwareDefaultSetting(defaultSeedColor, styleAndSource.first,
+ isWallpaper ? COLOR_SOURCE_HOME : COLOR_SOURCE_PRESET);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 1bc3096d0ac1..9475bdbd043b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2746,7 +2746,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
() -> mJavaAdapter,
() -> mSceneInteractor,
() -> mCommunalSceneInteractor,
- mKeyguardServiceShowLockscreenInteractor);
+ () -> mKeyguardServiceShowLockscreenInteractor);
setAlternateBouncerVisibility(false);
setPrimaryBouncerVisibility(false);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
index 4f7610ab7d72..f98c1309b24f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
@@ -25,7 +25,6 @@ import com.android.systemui.dagger.GlobalRootComponent
import com.android.systemui.dagger.SysUIComponent
import com.android.systemui.dump.dumpManager
import com.android.systemui.flags.systemPropertiesHelper
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.process.processWrapper
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -48,7 +47,7 @@ class SystemUIApplicationTest : SysuiTestCase() {
@get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
- val kosmos = Kosmos()
+ val kosmos = testKosmos()
@Mock private lateinit var initializer: SystemUIInitializer
@Mock private lateinit var rootComponent: GlobalRootComponent
@Mock private lateinit var sysuiComponent: SysUIComponent
@@ -56,9 +55,13 @@ class SystemUIApplicationTest : SysuiTestCase() {
@Mock private lateinit var initController: InitController
class StartableA : TestableStartable()
+
class StartableB : TestableStartable()
+
class StartableC : TestableStartable()
+
class StartableD : TestableStartable()
+
class StartableE : TestableStartable()
val dependencyMap: Map<Class<*>, Set<Class<out CoreStartable>>> =
@@ -114,7 +117,7 @@ class SystemUIApplicationTest : SysuiTestCase() {
.thenReturn(
mutableMapOf(
StartableA::class.java to Provider { startableA },
- StartableB::class.java to Provider { startableB }
+ StartableB::class.java to Provider { startableB },
)
)
app.onCreate()
@@ -130,7 +133,7 @@ class SystemUIApplicationTest : SysuiTestCase() {
mutableMapOf(
StartableC::class.java to Provider { startableC },
StartableA::class.java to Provider { startableA },
- StartableB::class.java to Provider { startableB }
+ StartableB::class.java to Provider { startableB },
)
)
app.onCreate()
@@ -150,7 +153,7 @@ class SystemUIApplicationTest : SysuiTestCase() {
StartableC::class.java to Provider { startableC },
StartableD::class.java to Provider { startableD },
StartableA::class.java to Provider { startableA },
- StartableB::class.java to Provider { startableB }
+ StartableB::class.java to Provider { startableB },
)
)
app.onCreate()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 1268de0f4141..4cfe106780f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -34,9 +34,9 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.EmptyTestActivity
import com.android.systemui.concurrency.fakeExecutor
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.testKosmos
import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -77,7 +77,7 @@ class TransitionAnimatorTest(
}
}
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
private val pathManager =
GoldenPathManager(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 0e68fce679b0..2c70249bcb06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -73,9 +73,9 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.displayStateRepository
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.withArgCaptor
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
@@ -189,7 +189,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
private lateinit var promptContentViewWithMoreOptionsButton:
PromptContentViewWithMoreOptionsButton
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt
index 9dab9d735603..b134dff89651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt
@@ -31,8 +31,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.motion.createSysUiComposeMotionTestRule
+import com.android.systemui.testKosmos
import com.android.systemui.utils.PolicyRestriction
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
@@ -61,7 +61,7 @@ import platform.test.screenshot.Displays.Phone
class BrightnessSliderMotionTest : SysuiTestCase() {
private val deviceSpec = DeviceEmulationSpec(Phone)
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
@get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos, deviceSpec)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
index a8bfbd18d0c5..356d445ab4d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
@@ -40,9 +40,9 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.android.wifitrackerlib.WifiEntry
@@ -66,7 +66,7 @@ import org.mockito.kotlin.whenever
@EnableFlags(Flags.FLAG_QS_TILE_DETAILED_VIEW)
@UiThreadTest
class InternetDetailsContentManagerTest : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
private val handler: Handler = kosmos.fakeExecutorHandler
private val scope: CoroutineScope = mock<CoroutineScope>()
private val telephonyManager: TelephonyManager = kosmos.telephonyManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java
index c231be181977..826c54787662 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java
@@ -23,6 +23,7 @@ import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
import android.os.UserHandle;
+import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -37,36 +38,34 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class RingtonePlayerTest extends SysuiTestCase {
- private AudioManager mAudioManager;
-
private static final String TAG = "RingtonePlayerTest";
- @Before
- public void setup() throws Exception {
- mAudioManager = getContext().getSystemService(AudioManager.class);
- }
-
@Test
public void testRingtonePlayerUriUserCheck() {
- android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer();
- final AudioAttributes aa = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build();
- // get a UserId that doesn't belong to mine
- final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0;
- // build a URI that I shouldn't have access to
- final Uri uri = new Uri.Builder()
- .scheme("content").authority(otherUserId + "@media")
- .appendPath("external").appendPath("downloads")
- .appendPath("bogusPathThatDoesNotMatter.mp3")
- .build();
- if (android.media.audio.Flags.ringtoneUserUriCheck()) {
- assertThrows(SecurityException.class, () ->
- irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/)
- );
+ // temporarily skipping this test
+ Log.i(TAG, "skipping testRingtonePlayerUriUserCheck");
+ return;
- assertThrows(SecurityException.class, () ->
- irp.getTitle(uri));
- }
+ // TODO change how IRingtonePlayer is created
+// android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer();
+// final AudioAttributes aa = new AudioAttributes.Builder()
+// .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build();
+// // get a UserId that doesn't belong to mine
+// final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0;
+// // build a URI that I shouldn't have access to
+// final Uri uri = new Uri.Builder()
+// .scheme("content").authority(otherUserId + "@media")
+// .appendPath("external").appendPath("downloads")
+// .appendPath("bogusPathThatDoesNotMatter.mp3")
+// .build();
+// if (android.media.audio.Flags.ringtoneUserUriCheck()) {
+// assertThrows(SecurityException.class, () ->
+// irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/)
+// );
+//
+// assertThrows(SecurityException.class, () ->
+// irp.getTitle(uri));
+// }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index b7040ee2a11e..020b5dd054b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -61,10 +62,6 @@ import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
-
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
@@ -265,7 +262,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(statusbarStateController, never()).setState(anyInt())
verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
verify(centralSurfaces)
- .showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject())
+ .showBouncerWithDimissAndCancelIfKeyguard(nullable(), nullable())
}
@Test
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 0c0ef9d5edfe..ee9a5f3befe0 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
@@ -67,6 +67,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.headsup.mockHeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor
import com.android.systemui.statusbar.notification.row.icon.appIconProvider
import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -146,6 +147,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
@Mock private lateinit var notificationManager: INotificationManager
@Mock private lateinit var shortcutManager: ShortcutManager
@Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+ @Mock private lateinit var packageDemotionInteractor: PackageDemotionInteractor
@Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
@Mock private lateinit var contextTracker: UserContextProvider
@Mock private lateinit var bubblesManager: BubblesManager
@@ -185,6 +187,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
launcherApps,
shortcutManager,
channelEditorDialogController,
+ packageDemotionInteractor,
contextTracker,
assistantFeedbackController,
Optional.of(bubblesManager),
@@ -438,6 +441,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ eq(packageDemotionInteractor),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -473,6 +477,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ eq(packageDemotionInteractor),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -508,6 +513,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ eq(packageDemotionInteractor),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
index 2e65478714af..12f7af106d10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
@@ -108,7 +108,7 @@ class FoldStateListenerTest : SysuiTestCase() {
private fun setGoToSleepStates(vararg states: Int) {
mContext.orCreateTestableResources.addOverride(
R.array.config_deviceStatesOnWhichToSleep,
- states
+ states,
)
}
@@ -117,9 +117,10 @@ class FoldStateListenerTest : SysuiTestCase() {
}
companion object {
- private val DEVICE_STATE_FOLDED = Kosmos().foldedDeviceStateList.first()
- private val DEVICE_STATE_HALF_FOLDED = Kosmos().halfFoldedDeviceState
- private val DEVICE_STATE_UNFOLDED = Kosmos().unfoldedDeviceState
+ private val kosmos = Kosmos()
+ private val DEVICE_STATE_FOLDED = kosmos.foldedDeviceStateList.first()
+ private val DEVICE_STATE_HALF_FOLDED = kosmos.halfFoldedDeviceState
+ private val DEVICE_STATE_UNFOLDED = kosmos.unfoldedDeviceState
private const val FOLDED = true
private const val NOT_FOLDED = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index a553b176c34a..6618843ab46d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -26,9 +26,8 @@ import com.android.internal.util.LatencyTracker
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.foldedDeviceStateList
-import com.android.systemui.halfFoldedDeviceState
import com.android.systemui.keyguard.ScreenLifecycle
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.util.FoldableDeviceStates
import com.android.systemui.unfold.util.FoldableTestUtils
import com.android.systemui.unfoldedDeviceState
@@ -70,12 +69,10 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(deviceStateManager.supportedDeviceStates).thenReturn(
- listOf(
- Kosmos().foldedDeviceStateList[0],
- Kosmos().unfoldedDeviceState
+ whenever(deviceStateManager.supportedDeviceStates)
+ .thenReturn(
+ listOf(testKosmos().foldedDeviceStateList[0], testKosmos().unfoldedDeviceState)
)
- )
unfoldLatencyTracker =
UnfoldLatencyTracker(
@@ -85,7 +82,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() {
context.mainExecutor,
context,
context.contentResolver,
- screenLifecycle
+ screenLifecycle,
)
.apply { init() }
@@ -206,7 +203,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() {
Settings.Global.putString(
context.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
- durationScale.toString()
+ durationScale.toString(),
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
index 5f3442048fcd..422b20e8b951 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
@@ -45,7 +45,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -92,7 +92,7 @@ class GradientColorWallpaperTest : SysuiTestCase() {
engine.onSurfaceRedrawNeeded(surfaceHolder)
- verifyZeroInteractions(canvas)
+ verifyNoMoreInteractions(canvas)
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt
index 3cec5a9cd822..361d21dc134d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.interactor
+package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
-val Kosmos.keyguardServiceShowLockscreenInteractor by
- Kosmos.Fixture { KeyguardServiceShowLockscreenInteractor(backgroundScope = testScope) }
+val Kosmos.keyguardServiceShowLockscreenRepository by
+ Kosmos.Fixture { KeyguardServiceShowLockscreenRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt
new file mode 100644
index 000000000000..447aa1255463
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardServiceShowLockscreenRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.userTracker
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.keyguardServiceShowLockscreenInteractor by
+ Kosmos.Fixture {
+ KeyguardServiceShowLockscreenInteractor(
+ backgroundScope = testScope,
+ selectedUserInteractor = selectedUserInteractor,
+ repository = keyguardServiceShowLockscreenRepository,
+ userTracker = userTracker,
+ wmLockscreenVisibilityInteractor = { windowManagerLockscreenVisibilityInteractor },
+ keyguardEnabledInteractor = keyguardEnabledInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt
index f7caeb69f17e..dd868e767d9b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt
@@ -18,10 +18,12 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
val Kosmos.keyguardShowWhileAwakeInteractor by
Kosmos.Fixture {
KeyguardShowWhileAwakeInteractor(
+ backgroundScope = testScope,
biometricSettingsRepository = biometricSettingsRepository,
keyguardEnabledInteractor = keyguardEnabledInteractor,
keyguardServiceShowLockscreenInteractor = keyguardServiceShowLockscreenInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
index 43fa718cff9a..c89fb705f417 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -27,7 +27,7 @@ import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.time.systemClock
-val Kosmos.keyguardWakeDirectlyToGoneInteractor by
+val Kosmos.keyguardWakeDirectlyToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor by
Kosmos.Fixture {
KeyguardWakeDirectlyToGoneInteractor(
applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt
new file mode 100644
index 000000000000..38b59945ab7d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.packageDemotionInteractor by Kosmos.Fixture { PackageDemotionInteractor() }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index d935626c34df..d8741975c71a 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -23,7 +23,9 @@ import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACK
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt;
import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault;
@@ -271,6 +273,13 @@ public class RavenwoodRuntimeEnvironmentController {
dumpJavaProperties();
dumpOtherInfo();
+ System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
+ var runtimePath = getRavenwoodRuntimePath();
+ System.setProperty(RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP, runtimePath);
+
+ Log.i(TAG, "PWD=" + System.getProperty("user.dir"));
+ Log.i(TAG, "RuntimePath=" + runtimePath);
+
// Make sure libravenwood_runtime is loaded.
System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
@@ -314,7 +323,6 @@ public class RavenwoodRuntimeEnvironmentController {
Typeface.loadPreinstalledSystemFontMap();
Typeface.loadNativeSystemFonts();
- System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
// This will let AndroidJUnit4 use the original runner.
System.setProperty("android.junit.runner",
"androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 893b354d4645..e1b537e11842 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -68,6 +68,8 @@ public class RavenwoodCommonUtils {
RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk";
public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
+ public static final String RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP =
+ "android.ravenwood.runtime_path";
/**
* @return if we're running on Ravenwood.
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
index fd6d6fb66465..b3af8753ee83 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
@@ -25,7 +25,6 @@ import com.android.internal.os.Flags
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,7 +39,6 @@ class RavenwoodAconfigSimpleReadTests {
}
@Test
- @Ignore // TODO: Enable this test after rolling out the "2" flags.
fun testTrueFlags() {
assertTrue(Flags.ravenwoodFlagRo2())
assertTrue(Flags.ravenwoodFlagRw2())
@@ -67,14 +65,12 @@ class RavenwoodAconfigCheckFlagsRuleTests {
@Test
@RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RO_2)
- @Ignore // TODO: Enable this test after rolling out the "2" flags.
fun testRequireFlagsDisabledRo() {
fail("This test shouldn't be executed")
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RW_2)
- @Ignore // TODO: Enable this test after rolling out the "2" flags.
fun testRequireFlagsDisabledRw() {
fail("This test shouldn't be executed")
}
diff --git a/ravenwood/tests/resapk_test/Android.bp b/ravenwood/tests/resapk_test/Android.bp
index c14576550f78..960b3ed0013a 100644
--- a/ravenwood/tests/resapk_test/Android.bp
+++ b/ravenwood/tests/resapk_test/Android.bp
@@ -10,7 +10,7 @@ package {
android_ravenwood_test {
name: "RavenwoodResApkTest",
- resource_apk: "RavenwoodResApkTest-apk",
+ resource_apk: "RavenwoodResApkTest-res",
libs: [
// Normally, tests shouldn't directly access it, but we need to access RavenwoodCommonUtils
@@ -24,6 +24,7 @@ android_ravenwood_test {
],
srcs: [
"test/**/*.java",
+ ":RavenwoodResApkTest-res{.aapt.srcjar}",
],
sdk_version: "test_current",
auto_gen_config: true,
diff --git a/ravenwood/tests/resapk_test/apk/Android.bp b/ravenwood/tests/resapk_test/apk/Android.bp
index 10ed5e2f8410..fd8976df4316 100644
--- a/ravenwood/tests/resapk_test/apk/Android.bp
+++ b/ravenwood/tests/resapk_test/apk/Android.bp
@@ -8,7 +8,13 @@ package {
}
android_app {
- name: "RavenwoodResApkTest-apk",
+ name: "RavenwoodResApkTest-res",
sdk_version: "current",
+
+ use_resource_processor: false,
+
+ flags_packages: [
+ "com.android.internal.os.flags-aconfig",
+ ],
}
diff --git a/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml b/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml
new file mode 100644
index 000000000000..17cdb868fc6b
--- /dev/null
+++ b/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <View android:id="@+id/view1" text="no-flags" />
+ <View android:id="@+id/view2" text="ro-enabled" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_2"/>
+ <View android:id="@+id/view3" text="ro-disabled" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_1"/>
+ <View android:id="@+id/view2" text="rw-enabled" android:featureFlag="com.android.internal.os.ravenwood_flag_rw_2"/>
+ <View android:id="@+id/view3" text="rw-disabled" android:featureFlag="com.android.internal.os.ravenwood_flag_rw_1"/>
+</LinearLayout>
diff --git a/ravenwood/tests/resapk_test/apk/res/values/strings.xml b/ravenwood/tests/resapk_test/apk/res/values/strings.xml
index 23d4c0f21007..5abf7475caa7 100644
--- a/ravenwood/tests/resapk_test/apk/res/values/strings.xml
+++ b/ravenwood/tests/resapk_test/apk/res/values/strings.xml
@@ -13,7 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Test string 1 -->
<string name="test_string_1" translatable="false" >Test String 1</string>
+ <!-- values can only use readonly flags -->
+ <string name="test_string_enabled" translatable="false" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_2">Enabled</string>
+ <string name="test_string_disabled" translatable="false" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_1">Disabled</string>
</resources>
diff --git a/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java b/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
index e547114bbe40..89f8d40da7d4 100644
--- a/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
+++ b/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
@@ -16,23 +16,41 @@
package com.android.ravenwoodtest.resapk_test;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.util.Log;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.ravenwood.common.RavenwoodCommonUtils;
+import com.android.ravenwood.restest_apk.R;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class RavenwoodResApkTest {
+ private static final String TAG = "RavenwoodResApkTest";
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
/**
* Ensure the file "ravenwood-res.apk" exists.
- * TODO Check the content of it, once Ravenwood supports resources. The file should
- * be a copy of RavenwoodResApkTest-apk.apk
*/
@Test
public void testResApkExists() {
@@ -48,4 +66,73 @@ public class RavenwoodResApkTest {
assertTrue(new File(
RavenwoodCommonUtils.getRavenwoodRuntimePath() + "/" + file).exists());
}
+
+ @Test
+ public void testReadStringNoFlag() {
+ assertThat(sContext.getString(R.string.test_string_1)).isEqualTo("Test String 1");
+ }
+
+ @Test
+ public void testReadStringRoFlagEnabled() {
+ assertThat(sContext.getString(R.string.test_string_enabled)).isEqualTo("Enabled");
+ }
+
+ @Test
+ public void testReadStringRoFlagDisabled() {
+ assertThrows(android.content.res.Resources.NotFoundException.class, () -> {
+ sContext.getString(R.string.test_string_disabled);
+ });
+ }
+
+ /**
+ * Look into the layout and collect the "text" attribute.
+ *
+ * It _should_ respect android:featureFlag, but until b/396458006 gets fixed, this returns
+ * even disabled elements.
+ */
+ private List<String> getTextsFromEnabledChildren() throws Exception {
+ try (XmlResourceParser parser = sContext.getResources().getLayout(R.layout.testlayout)) {
+ assertNotNull(parser);
+
+ var ret = new ArrayList<String>();
+
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ var text = parser.getAttributeValue(null, "text");
+ if (text == null) {
+ continue;
+ }
+
+ Log.d(TAG, "Found tag: " + parser.getName() + " text='" + text + "'");
+ ret.add(text);
+ }
+ return ret;
+ }
+ }
+
+ @Test
+ public void testElementNoFlag() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).contains("no-flags");
+ }
+
+ @Test
+ public void testElementWithRoFlagEnabled() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).contains("ro-enabled");
+ }
+
+ @Test
+ public void testElementWithRoFlagDisabled() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).doesNotContain("ro-disabled");
+ }
+
+ @Test
+ public void testElementWithRwFlagEnabled() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).contains("rw-enabled");
+ }
+
+ @Test
+ @DisabledOnRavenwood(bug = 396458006,
+ reason = "RW flags in XML are all handled as enabled for now")
+ public void testElementWithRwFlagDisabled() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).doesNotContain("rw-disabled");
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6c26c1f74002..703e37fad5ad 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -531,7 +531,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
AccessibilitySecurityPolicy securityPolicy,
SystemActionPerformer systemActionPerformer,
AccessibilityWindowManager a11yWindowManager,
- AccessibilityDisplayListener a11yDisplayListener,
+ AccessibilityDisplayListener.DisplayManagerWrapper displayManagerWrapper,
MagnificationController magnificationController,
@Nullable AccessibilityInputFilter inputFilter,
ProxyManager proxyManager,
@@ -550,7 +550,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mSecurityPolicy = securityPolicy;
mSystemActionPerformer = systemActionPerformer;
mA11yWindowManager = a11yWindowManager;
- mA11yDisplayListener = a11yDisplayListener;
+ mA11yDisplayListener = new AccessibilityDisplayListener(displayManagerWrapper,
+ new MainHandler(Looper.getMainLooper()));
mMagnificationController = magnificationController;
mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
@@ -596,7 +597,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
this, LocalServices.getService(PackageManagerInternal.class));
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
mWindowManagerService, this, mSecurityPolicy, this, mTraceManager);
- mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
+ mA11yDisplayListener = new AccessibilityDisplayListener(
+ new AccessibilityDisplayListener.DisplayManagerWrapper(mContext), mMainHandler);
mMagnificationController = new MagnificationController(
this,
mLock,
@@ -5457,11 +5459,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* A Utility class to handle display state.
*/
public class AccessibilityDisplayListener implements DisplayManager.DisplayListener {
- private final DisplayManager mDisplayManager;
+ private final DisplayManagerWrapper mDisplayManager;
private final ArrayList<Display> mDisplaysList = new ArrayList<>();
private int mSystemUiUid = 0;
- AccessibilityDisplayListener(Context context, Handler handler) {
+ AccessibilityDisplayListener(DisplayManagerWrapper displayManager, Handler handler) {
// Avoid concerns about one thread adding displays while another thread removes
// them by ensuring the looper is the main looper and the DisplayListener
// callbacks are always executed on the one main thread.
@@ -5474,7 +5476,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Slog.e(LOG_TAG, errorMessage);
}
- mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager = displayManager;
mDisplayManager.registerDisplayListener(this, handler);
initializeDisplayList();
@@ -5626,6 +5628,34 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
return true;
}
+
+ /** Wrapper of DisplayManager for testing. */
+ @VisibleForTesting
+ static class DisplayManagerWrapper {
+ private final DisplayManager mDm;
+
+ DisplayManagerWrapper(Context context) {
+ mDm = context.getSystemService(DisplayManager.class);
+ }
+
+ /**
+ * @see DisplayManager#registerDisplayListener(DisplayManager.DisplayListener, Handler)
+ */
+ public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
+ @Nullable Handler handler) {
+ mDm.registerDisplayListener(listener, handler);
+ }
+
+ /** @see DisplayManager#getDisplays() */
+ public Display[] getDisplays() {
+ return mDm.getDisplays();
+ }
+
+ /** @see DisplayManager#getDisplay(int) */
+ public Display getDisplay(int displayId) {
+ return mDm.getDisplay(displayId);
+ }
+ }
}
/** Represents an {@link AccessibilityManager} */
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 4b6d6bc955cc..fd2d8352eb4f 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -61,7 +61,7 @@ per-file *Oom* = file:/OOM_ADJUSTER_OWNERS
per-file ProcessStateController.java = file:/OOM_ADJUSTER_OWNERS
# Miscellaneous
-per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com
+per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com
per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS
# Activity Security
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 8ef79a916530..4b5f06b13885 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1472,8 +1472,8 @@ public class AudioDeviceBroker {
mAudioService.postAccessoryPlugMediaUnmute(device);
}
- /*package*/ int getVssVolumeForDevice(int streamType, int device) {
- return mAudioService.getVssVolumeForDevice(streamType, device);
+ /*package*/ int getVolumeForDeviceIgnoreMute(int streamType, int device) {
+ return mAudioService.getVolumeForDeviceIgnoreMute(streamType, device);
}
/*package*/ int getMaxVssVolumeForStream(int streamType) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 829d9ea7495f..2e6d98485e85 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -2482,7 +2482,7 @@ public class AudioDeviceInventory {
@GuardedBy("mDevicesLock")
private void makeHearingAidDeviceAvailable(
String address, String name, int streamType, String eventSource) {
- final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
+ final int hearingAidVolIndex = mDeviceBroker.getVolumeForDeviceIgnoreMute(streamType,
DEVICE_OUT_HEARING_AID);
mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
@@ -2672,7 +2672,7 @@ public class AudioDeviceInventory {
}
final int leAudioVolIndex = (volumeIndex == -1)
- ? mDeviceBroker.getVssVolumeForDevice(streamType, device)
+ ? mDeviceBroker.getVolumeForDeviceIgnoreMute(streamType, device)
: volumeIndex;
final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 766456134b20..d917bffa06e9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -529,7 +529,7 @@ public class AudioService extends IAudioService.Stub
*/
private InputDeviceVolumeHelper mInputDeviceVolumeHelper;
- /*package*/ int getVssVolumeForDevice(int stream, int device) {
+ /*package*/ int getVolumeForDeviceIgnoreMute(int stream, int device) {
final VolumeStreamState streamState = mStreamStates.get(stream);
return streamState != null ? streamState.getIndex(device) : -1;
}
@@ -5100,7 +5100,7 @@ public class AudioService extends IAudioService.Stub
}
final int device = absVolumeDevices.toArray(new Integer[0])[0].intValue();
- final int index = getStreamVolume(streamType, device);
+ final int index = (getVolumeForDeviceIgnoreMute(streamType, device) + 5) / 10;
if (DEBUG_VOL) {
Slog.i(TAG, "onUpdateContextualVolumes streamType: " + streamType
@@ -15109,6 +15109,61 @@ public class AudioService extends IAudioService.Stub
/**
* @hide
+ * Returns the current audio output device delay value of key in milliseconds.
+ *
+ * In aidl implementation, the param of getParameters is "key" and reply delay of all supported
+ * devices which be composited as "key=device,address,delay|device,address,delay|...".
+ * e.g.
+ * param: additional_output_device_delay=
+ * reply: additional_output_device_delay=2,,0|4,,0|8,,0|128,,0|256,,0|512,,0|1024,,0|8192,,0|
+ * 16384,,0|262144,,0|262145,,0|524288,,0|67108864,,0|134217728,,0|536870912,,0|536870913,,0|
+ * 536870914,,0
+ *
+ * In hidl implementation, the param of getParameters is "key=device,address" and reply a
+ * specific device delay which be composited as "key=device,address,delay".
+ * e.g.
+ * param: additional_output_device_delay=2,
+ * reply: additional_output_device_delay=2,,0
+ *
+ * @param key
+ * @param deviceType
+ * @param address
+ * @return the delay value of key. This is a non-negative number.
+ * {@code 0} is returned if unsupported.
+ */
+ private long getDelayByKeyDevice(@NonNull String key, @NonNull AudioDeviceAttributes device) {
+ long delayMillis = 0;
+
+ try {
+ if (AudioHalVersionInfo.AUDIO_HAL_TYPE_AIDL == getHalVersion().getHalType()) {
+ final String reply = AudioSystem.getParameters(key);
+ final String keyDeviceAddressPrefix =
+ Integer.toUnsignedString(device.getInternalType()) + "," + device.getAddress() +
+ ",";
+ int start = reply.indexOf(keyDeviceAddressPrefix);
+ int end = -1;
+ if (start != -1) {
+ start += keyDeviceAddressPrefix.length();
+ end = reply.indexOf("|", start);
+ delayMillis = Long.parseLong(
+ end == -1 ? reply.substring(start) : reply.substring(start, end));
+ }
+ } else {
+ final String reply = AudioSystem.getParameters(
+ key + "=" + device.getInternalType() + "," + device.getAddress());
+ if (reply.contains(key)) {
+ delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+ }
+ }
+ } catch (NullPointerException e) {
+ Log.w(TAG, "NullPointerException when getting delay for device " + device, e);
+ }
+
+ return delayMillis;
+ }
+
+ /**
+ * @hide
* Returns the current additional audio output device delay in milliseconds.
*
* @param deviceType
@@ -15124,17 +15179,8 @@ public class AudioService extends IAudioService.Stub
device = retrieveBluetoothAddress(device);
final String key = "additional_output_device_delay";
- final String reply = AudioSystem.getParameters(
- key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis = 0;
- if (reply.contains(key)) {
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
- }
- }
- return delayMillis;
+
+ return getDelayByKeyDevice(key, device);
}
/**
@@ -15156,17 +15202,8 @@ public class AudioService extends IAudioService.Stub
device = retrieveBluetoothAddress(device);
final String key = "max_additional_output_device_delay";
- final String reply = AudioSystem.getParameters(
- key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis = 0;
- if (reply.contains(key)) {
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
- }
- }
- return delayMillis;
+
+ return getDelayByKeyDevice(key, device);
}
@android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING)
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 643f3308d8f5..67afff79dffd 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -724,7 +724,7 @@ public class SoundDoseHelper {
int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
if (safeDevicesContains(device) && isStreamActive) {
scheduleMusicActiveCheck();
- int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
+ int index = mAudioService.getVolumeForDeviceIgnoreMute(AudioSystem.STREAM_MUSIC,
device);
if (index > safeMediaVolumeIndex(device)) {
// Approximate cumulative active music time
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 2d937bdcc683..6db62c8397f3 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -171,7 +171,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
}
- public boolean isInMessageHistory(HubMessage message) {
+ public boolean isInReliableMessageHistory(HubMessage message) {
+ if (!message.isResponseRequired()) return false;
// Clean up the history
Iterator<Map.Entry<Integer, Long>> iterator =
mRxMessageHistoryMap.entrySet().iterator();
@@ -188,7 +189,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
return mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber());
}
- public void addMessageToHistory(HubMessage message) {
+ public void addReliableMessageToHistory(HubMessage message) {
+ if (!message.isResponseRequired()) return;
if (mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber())) {
long value = mRxMessageHistoryMap.get(message.getMessageSequenceNumber());
Log.w(
@@ -623,7 +625,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
return ErrorCode.PERMANENT_ERROR;
}
HubEndpointInfo remote = mSessionMap.get(sessionId).getRemoteEndpointInfo();
- if (mSessionMap.get(sessionId).isInMessageHistory(message)) {
+ if (mSessionMap.get(sessionId).isInReliableMessageHistory(message)) {
Log.e(TAG, "Dropping duplicate message: " + message);
return ErrorCode.TRANSIENT_ERROR;
}
@@ -648,7 +650,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
boolean success =
invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message));
if (success) {
- mSessionMap.get(sessionId).addMessageToHistory(message);
+ mSessionMap.get(sessionId).addReliableMessageToHistory(message);
}
return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR;
}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 27ca83addec8..126b3a7c721f 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -351,18 +351,38 @@ public final class StorageUserConnection {
}
}
+
private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception {
+ waitForAsyncVoid(asyncCall, /*bindIfNotConnected*/ true,
+ DEFAULT_REMOTE_TIMEOUT_SECONDS);
+ }
+
+ private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall,
+ boolean bindIfNotConnected, int timeoutSeconds) throws Exception {
CompletableFuture<Void> opFuture = new CompletableFuture<>();
RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture));
- waitForAsync(asyncCall, callback, opFuture, mOutstandingOps,
- DEFAULT_REMOTE_TIMEOUT_SECONDS);
+ waitForAsync(asyncCall, callback, opFuture, mOutstandingOps, bindIfNotConnected,
+ timeoutSeconds);
}
private <T> T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback,
CompletableFuture<T> opFuture, ArrayList<CompletableFuture<T>> outstandingOps,
- long timeoutSeconds) throws Exception {
- CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
+ boolean bindIfNotConnected, long timeoutSeconds) throws Exception {
+
+ CompletableFuture<IExternalStorageService> serviceFuture;
+ if (bindIfNotConnected) {
+ serviceFuture = connectIfNeeded();
+ } else {
+ synchronized (mLock) {
+ if (mRemoteFuture == null || mRemoteFuture.getNow(null) == null) {
+ Slog.w(TAG, "Dropping async request as service is not connected"
+ + "and request doesn't require connecting");
+ return null;
+ }
+ serviceFuture = mRemoteFuture;
+ }
+ }
try {
synchronized (mLock) {
@@ -404,7 +424,11 @@ public final class StorageUserConnection {
public void endSession(Session session) throws ExternalStorageServiceException {
try {
waitForAsyncVoid((service, callback) ->
- service.endSession(session.sessionId, callback));
+ service.endSession(session.sessionId, callback),
+ // endSession shouldn't be trying to bind to remote service if the service
+ // isn't connected already as this means that no previous mounting has been
+ // completed.
+ /*bindIfNotConnected*/ false, /*timeoutSeconds*/ 10);
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to end session: " + session, e);
}
@@ -415,7 +439,11 @@ public final class StorageUserConnection {
ExternalStorageServiceException {
try {
waitForAsyncVoid((service, callback) ->
- service.notifyVolumeStateChanged(sessionId, vol, callback));
+ service.notifyVolumeStateChanged(sessionId, vol, callback),
+ // notifyVolumeStateChanged shouldn't be trying to bind to remote service
+ // if the service isn't connected already as this means that
+ // no previous mounting has been completed
+ /*bindIfNotConnected*/ false, /*timeoutSeconds*/ 10);
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to notify volume state changed "
+ "for vol : " + vol, e);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 919b14b6db62..df00fa195f03 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5590,6 +5590,14 @@ final class ActivityRecord extends WindowToken {
commitVisibility(visible, performLayout, false /* fromTransition */);
}
+ /**
+ * Sets whether safe region bounds are needed for the Activity. This is called from
+ * {@link ActivityStarter} after the source record is created.
+ */
+ void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) {
+ mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(needsSafeRegionBounds);
+ }
+
/** Updates draw state and shows drawn windows. */
void commitFinishDrawing(SurfaceControl.Transaction t) {
boolean committed = false;
@@ -7753,9 +7761,11 @@ final class ActivityRecord extends WindowToken {
final AppCompatAspectRatioPolicy aspectRatioPolicy =
mAppCompatController.getAspectRatioPolicy();
aspectRatioPolicy.reset();
+ final AppCompatSafeRegionPolicy safeRegionPolicy =
+ mAppCompatController.getSafeRegionPolicy();
mAppCompatController.getLetterboxPolicy().resetFixedOrientationLetterboxEligibility();
mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
- isFixedRotationTransforming());
+ isFixedRotationTransforming(), safeRegionPolicy.getLatestSafeRegionBounds());
// Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be
// different from windowing mode of the task (PiP) during transition from fullscreen to PiP
@@ -7801,6 +7811,13 @@ final class ActivityRecord extends WindowToken {
}
}
+ // If activity can be letterboxed due to a safe region only, use the safe region bounds
+ // as the resolved bounds. We ignore cases where the letterboxing can happen due to other
+ // app compat conditions and a safe region since the safe region app compat is sandboxed
+ // earlier in TaskFragment.ConfigOverrideHint.resolveTmpOverrides.
+ mAppCompatController.getSafeRegionPolicy().resolveSafeRegionBoundsConfigurationIfNeeded(
+ resolvedConfig, newParentConfiguration);
+
if (isFixedOrientationLetterboxAllowed
|| scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
// In fullscreen, can be letterboxed for aspect ratio.
@@ -7980,7 +7997,7 @@ final class ActivityRecord extends WindowToken {
mAppCompatController.getSizeCompatModePolicy();
final Rect screenResolvedBounds = scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ final Rect parentBounds = mResolveConfigHint.mParentBoundsOverride;
final float screenResolvedBoundsWidth = screenResolvedBounds.width();
final float parentAppBoundsWidth = parentAppBounds.width();
final boolean isImmersiveMode = isImmersiveMode(parentBounds);
@@ -8167,7 +8184,7 @@ final class ActivityRecord extends WindowToken {
* in this method.
*/
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
- final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
+ final Rect parentBounds = mResolveConfigHint.mParentBoundsOverride;
final Rect stableBounds = new Rect();
final Rect outNonDecorBounds = mTmpBounds;
// If orientation is respected when insets are applied, then stableBounds will be empty.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index a84a008f66eb..92f51bed419f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2203,6 +2203,9 @@ class ActivityStarter {
? mLaunchParams.mPreferredTaskDisplayArea
: mRootWindowContainer.getDefaultTaskDisplayArea();
mPreferredWindowingMode = mLaunchParams.mWindowingMode;
+ if (mLaunchParams.mNeedsSafeRegionBounds != null) {
+ r.setNeedsSafeRegionBounds(mLaunchParams.mNeedsSafeRegionBounds);
+ }
}
private TaskDisplayArea computeSuggestedLaunchDisplayArea(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b0563128870a..b7ef1057388c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -36,6 +36,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.ACTION_VIEW;
@@ -1634,16 +1635,19 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
}
- private void moveHomeRootTaskToFrontIfNeeded(int flags, TaskDisplayArea taskDisplayArea,
+ @VisibleForTesting
+ void moveHomeRootTaskToFrontIfNeeded(int flags, TaskDisplayArea taskDisplayArea,
String reason) {
final Task focusedRootTask = taskDisplayArea.getFocusedRootTask();
if ((taskDisplayArea.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
&& (flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0)
- || (focusedRootTask != null && focusedRootTask.isActivityTypeRecents())) {
+ || (focusedRootTask != null && focusedRootTask.isActivityTypeRecents()
+ && focusedRootTask.getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) {
// We move root home task to front when we are on a fullscreen display area and
// caller has requested the home activity to move with it. Or the previous root task
- // is recents.
+ // is recents and we are not on multi-window mode.
+
taskDisplayArea.moveHomeRootTaskToFront(reason);
}
}
@@ -2830,6 +2834,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
"startActivityFromRecents: Task " + taskId + " not found.");
}
+
+ if (task.getRootTask() != null
+ && task.getRootTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ // Don't move home forward if task is in multi window mode
+ moveHomeTaskForward = false;
+ }
+
if (moveHomeTaskForward) {
// We always want to return to the home activity instead of the recents
// activity from whatever is started from the recents activity, so move
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 3535a96d9c45..d6f058a34367 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -240,7 +240,7 @@ class AppCompatAspectRatioPolicy {
final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
final Rect parentAppBounds =
mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ final Rect parentBounds = mActivityRecord.mResolveConfigHint.mParentBoundsOverride;
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
// Use tmp bounds to calculate aspect ratio so we can know whether the activity should
// use restricted size (resolved bounds may be the requested override bounds).
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index c479591a5e0d..28f9a33d65d3 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -33,6 +33,8 @@ class AppCompatController {
@NonNull
private final AppCompatAspectRatioPolicy mAspectRatioPolicy;
@NonNull
+ private final AppCompatSafeRegionPolicy mSafeRegionPolicy;
+ @NonNull
private final AppCompatReachabilityPolicy mReachabilityPolicy;
@NonNull
private final DesktopAppCompatAspectRatioPolicy mDesktopAspectRatioPolicy;
@@ -62,6 +64,7 @@ class AppCompatController {
mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
mAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
mTransparentPolicy, mAppCompatOverrides);
+ mSafeRegionPolicy = new AppCompatSafeRegionPolicy(activityRecord, packageManager);
mReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
wmService.mAppCompatConfiguration);
mLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord,
@@ -90,6 +93,11 @@ class AppCompatController {
}
@NonNull
+ AppCompatSafeRegionPolicy getSafeRegionPolicy() {
+ return mSafeRegionPolicy;
+ }
+
+ @NonNull
DesktopAppCompatAspectRatioPolicy getDesktopAspectRatioPolicy() {
return mDesktopAspectRatioPolicy;
}
@@ -163,6 +171,6 @@ class AppCompatController {
getTransparentPolicy().dump(pw, prefix);
getLetterboxPolicy().dump(pw, prefix);
getSizeCompatModePolicy().dump(pw, prefix);
+ getSafeRegionPolicy().dump(pw, prefix);
}
-
}
diff --git a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
new file mode 100644
index 000000000000..959609309da1
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
@@ -0,0 +1,167 @@
+/*
+ * 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;
+
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
+
+/**
+ * Encapsulate app compat policy logic related to a safe region.
+ */
+class AppCompatSafeRegionPolicy {
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ final PackageManager mPackageManager;
+ // Whether the Activity needs to be in the safe region bounds.
+ private boolean mNeedsSafeRegionBounds = false;
+ // Denotes the latest safe region bounds. Can be empty if the activity or the ancestors do
+ // not have any safe region bounds.
+ @NonNull
+ private final Rect mLatestSafeRegionBounds = new Rect();
+ // Whether the activity has allowed safe region letterboxing. This can be set through the
+ // manifest and the default value is true.
+ @NonNull
+ private final BooleanSupplier mAllowSafeRegionLetterboxing;
+
+ AppCompatSafeRegionPolicy(@NonNull ActivityRecord activityRecord,
+ @NonNull PackageManager packageManager) {
+ mActivityRecord = activityRecord;
+ mPackageManager = packageManager;
+ mAllowSafeRegionLetterboxing = AppCompatUtils.asLazy(() -> {
+ // Application level property.
+ if (allowSafeRegionLetterboxing(packageManager)) {
+ return true;
+ }
+ // Activity level property.
+ try {
+ return packageManager.getPropertyAsUser(
+ PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
+ mActivityRecord.mActivityComponent.getPackageName(),
+ mActivityRecord.mActivityComponent.getClassName(),
+ mActivityRecord.mUserId).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return true;
+ }
+ });
+ }
+
+ private boolean allowSafeRegionLetterboxing(PackageManager pm) {
+ try {
+ return pm.getPropertyAsUser(
+ PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
+ mActivityRecord.packageName,
+ /* className */ null,
+ mActivityRecord.mUserId).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return true;
+ }
+ }
+
+ /**
+ * Computes the latest safe region bounds in
+ * {@link ActivityRecord#resolveOverrideConfiguration(Configuration)} since the activity has not
+ * been attached to the parent container when the ActivityRecord is instantiated. Note that the
+ * latest safe region bounds will be empty if activity has not allowed safe region letterboxing.
+ *
+ * @return latest safe region bounds as set on an ancestor window container.
+ */
+ public Rect getLatestSafeRegionBounds() {
+ if (!allowSafeRegionLetterboxing()) {
+ mLatestSafeRegionBounds.setEmpty();
+ return null;
+ }
+ // Get the latest safe region bounds since the bounds could have changed
+ final Rect latestSafeRegionBounds = mActivityRecord.getSafeRegionBounds();
+ if (latestSafeRegionBounds != null) {
+ mLatestSafeRegionBounds.set(latestSafeRegionBounds);
+ } else {
+ mLatestSafeRegionBounds.setEmpty();
+ }
+ return latestSafeRegionBounds;
+ }
+
+ /**
+ * Computes bounds when letterboxing is required only for the safe region bounds if needed.
+ */
+ public void resolveSafeRegionBoundsConfigurationIfNeeded(@NonNull Configuration resolvedConfig,
+ @NonNull Configuration newParentConfig) {
+ if (mLatestSafeRegionBounds.isEmpty()) {
+ return;
+ }
+ // If activity can not be letterboxed for a safe region only or it has not been attached
+ // to a WindowContainer yet.
+ if (!isLetterboxedForSafeRegionOnlyAllowed() || mActivityRecord.getParent() == null) {
+ return;
+ }
+ resolvedConfig.windowConfiguration.setBounds(mLatestSafeRegionBounds);
+ mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfig);
+ }
+
+ /**
+ * Safe region bounds can either be applied along with size compat, fixed orientation or
+ * aspect ratio conditions by sandboxing them to the safe region bounds. Or it can be applied
+ * independently when no other letterboxing condition is triggered. This method helps detecting
+ * the latter case.
+ *
+ * @return {@code true} if this application or activity has allowed safe region letterboxing and
+ * can be letterboxed only due to the safe region being set on the current or ancestor window
+ * container.
+ */
+ boolean isLetterboxedForSafeRegionOnlyAllowed() {
+ return !mActivityRecord.areBoundsLetterboxed() && getNeedsSafeRegionBounds()
+ && getLatestSafeRegionBounds() != null;
+ }
+
+ /**
+ * Set {@code true} if this activity needs to be within the safe region bounds, else false.
+ */
+ public void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) {
+ mNeedsSafeRegionBounds = needsSafeRegionBounds;
+ }
+
+ /**
+ * @return {@code true} if this activity needs to be within the safe region bounds.
+ */
+ public boolean getNeedsSafeRegionBounds() {
+ return mNeedsSafeRegionBounds;
+ }
+
+ /** @see android.view.WindowManager#PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING */
+ boolean allowSafeRegionLetterboxing() {
+ return mAllowSafeRegionLetterboxing.getAsBoolean();
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ if (mNeedsSafeRegionBounds) {
+ pw.println(prefix + " mNeedsSafeRegionBounds=true");
+ }
+ if (isLetterboxedForSafeRegionOnlyAllowed()) {
+ pw.println(prefix + " isLetterboxForSafeRegionOnlyAllowed=true");
+ }
+ if (!allowSafeRegionLetterboxing()) {
+ pw.println(prefix + " allowSafeRegionLetterboxing=false");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 80df081a6271..f872286726c3 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -242,6 +242,10 @@ final class AppCompatUtils {
if (aspectRatioPolicy.isLetterboxedForAspectRatioOnly()) {
return "ASPECT_RATIO";
}
+ if (activityRecord.mAppCompatController.getSafeRegionPolicy()
+ .isLetterboxedForSafeRegionOnlyAllowed()) {
+ return "SAFE_REGION";
+ }
return "UNKNOWN_REASON";
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index ce3ad889a308..d9354323ae7c 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -73,9 +73,12 @@ public final class DesktopModeBoundsCalculator {
final Rect stableBounds = new Rect();
task.getDisplayArea().getStableRect(stableBounds);
- // If the options bounds size is flexible, update size with calculated desired size.
+ final boolean hasFullscreenOverride = activity != null
+ && activity.mAppCompatController.getAspectRatioOverrides().hasFullscreenOverride();
+ // If the options bounds size is flexible and no fullscreen override has been applied,
+ // update size with calculated desired size.
final boolean updateOptionBoundsSize = options != null
- && options.getFlexibleLaunchSize();
+ && options.getFlexibleLaunchSize() && !hasFullscreenOverride;
// If cascading is also enabled, the position of the options bounds must be respected
// during the size update.
final boolean shouldRespectOptionPosition =
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 6698d2ec7cc4..03ba1a51ad7b 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -113,14 +113,23 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
// Copy over any values
outParams.set(currentParams);
- // In Proto2, trampoline task launches of an existing background task can result in the
- // previous windowing mode to be restored even if the desktop mode state has changed.
- // Let task launches inherit the windowing mode from the source task if available, which
- // should have the desired windowing mode set by WM Shell. See b/286929122.
if (source != null && source.getTask() != null) {
final Task sourceTask = source.getTask();
- outParams.mWindowingMode = sourceTask.getWindowingMode();
- appendLog("inherit-from-source=" + outParams.mWindowingMode);
+ if (DesktopModeFlags.DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX.isTrue()
+ && isEnteringDesktopMode(sourceTask, options, currentParams)) {
+ // If trampoline source is not freeform but we are entering or in desktop mode,
+ // ignore the source windowing mode and set the windowing mode to freeform
+ outParams.mWindowingMode = WINDOWING_MODE_FREEFORM;
+ appendLog("freeform window mode applied to task trampoline");
+ } else {
+ // In Proto2, trampoline task launches of an existing background task can result in
+ // the previous windowing mode to be restored even if the desktop mode state has
+ // changed. Let task launches inherit the windowing mode from the source task if
+ // available, which should have the desired windowing mode set by WM Shell.
+ // See b/286929122.
+ outParams.mWindowingMode = sourceTask.getWindowingMode();
+ appendLog("inherit-from-source=" + outParams.mWindowingMode);
+ }
}
if (phase == PHASE_WINDOWING_MODE) {
@@ -133,6 +142,11 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
}
if ((options == null || options.getLaunchBounds() == null) && task.hasOverrideBounds()) {
+ if (DesktopModeFlags.DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX.isTrue()) {
+ // We are in desktop, return result done to prevent other modifiers from modifying
+ // exiting task bounds or resolved windowing mode.
+ return RESULT_DONE;
+ }
appendLog("current task has bounds set, not overriding");
return RESULT_SKIP;
}
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 25fdf89afad1..2798e843d6dd 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -211,14 +211,17 @@ class Dimmer {
* child should call setAppearance again to request the Dim to continue.
* If multiple containers call this method, only the changes relative to the topmost will be
* applied.
+ * The creation of the dim layer is delayed if the requested dim and blur are 0.
* @param dimmingContainer Container requesting the dim
* @param alpha Dim amount
* @param blurRadius Blur amount
*/
protected void adjustAppearance(@NonNull WindowState dimmingContainer,
float alpha, int blurRadius) {
- final DimState d = obtainDimState(dimmingContainer);
- d.prepareLookChange(alpha, blurRadius);
+ if (mDimState != null || (alpha != 0 || blurRadius != 0)) {
+ final DimState d = obtainDimState(dimmingContainer);
+ d.prepareLookChange(alpha, blurRadius);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 76a39d9c884a..afacae8d0c13 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2161,7 +2161,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Re-show the previously hidden windows if all seamless rotated windows are done. */
void finishAsyncRotationIfPossible() {
final AsyncRotationController controller = mAsyncRotationController;
- if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
+ if (controller != null) {
controller.completeAll();
mAsyncRotationController = null;
}
@@ -2238,11 +2238,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
private void applyRotation(final int oldRotation, final int rotation) {
mDisplayRotation.applyCurrentRotation(rotation);
- final boolean shellTransitions = mTransitionController.getTransitionPlayer() != null;
- final boolean rotateSeamlessly =
- mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
- final Transaction transaction =
- shellTransitions ? getSyncTransaction() : getPendingTransaction();
+
// We need to update our screen size information to match the new rotation. If the rotation
// has actually changed then this method will return true and, according to the comment at
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -2250,25 +2246,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// #computeScreenConfiguration() later.
updateDisplayAndOrientation(null /* outConfig */);
- if (!shellTransitions) {
- forAllWindows(w -> {
- w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
- }, true /* traverseTopToBottom */);
- mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
- if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
- // Make sure DisplayRotation#isRotatingSeamlessly() will return false.
- mDisplayRotation.cancelSeamlessRotation();
- }
- }
+ // Before setDisplayProjection is applied by the start transaction of transition,
+ // set the transform hint to avoid using surface in old rotation.
+ setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation);
+ // The sync transaction should already contains setDisplayProjection, so unset the
+ // hint to restore the natural state when the transaction is applied.
+ getSyncTransaction().unsetFixedTransformHint(mSurfaceControl);
- if (shellTransitions) {
- // Before setDisplayProjection is applied by the start transaction of transition,
- // set the transform hint to avoid using surface in old rotation.
- setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation);
- // The sync transaction should already contains setDisplayProjection, so unset the
- // hint to restore the natural state when the transaction is applied.
- transaction.unsetFixedTransformHint(mSurfaceControl);
- }
scheduleAnimation();
mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation);
@@ -2870,8 +2854,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// If the transition finished callback cannot match the token for some reason, make sure the
// rotated state is cleared if it is already invisible.
if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
- && !mFixedRotationLaunchingApp.isVisible()
- && !mDisplayRotation.isRotatingSeamlessly()) {
+ && !mFixedRotationLaunchingApp.isVisible()) {
clearFixedRotationLaunchingApp();
}
// If there won't be a transition to notify the launch is done, then it should be ready to
@@ -5072,7 +5055,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
- if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) {
+ if (!inTransition()) {
mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
mLastHasContent,
mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 9cf792d82f56..9edbb70c3b74 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -168,19 +168,6 @@ public class DisplayRotation {
private int mDeferredRotationPauseCount;
/**
- * A count of the windows which are 'seamlessly rotated', e.g. a surface at an old orientation
- * is being transformed. We freeze orientation updates while any windows are seamlessly rotated,
- * so we need to track when this hits zero so we can apply deferred orientation updates.
- */
- private int mSeamlessRotationCount;
-
- /**
- * True in the interval from starting seamless rotation until the last rotated window draws in
- * the new orientation.
- */
- private boolean mRotatingSeamlessly;
-
- /**
* Behavior of rotation suggestions.
*
* @see Settings.Secure#SHOW_ROTATION_SUGGESTIONS
@@ -630,15 +617,6 @@ public class DisplayRotation {
return true;
}
- if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
- // The screen rotation animation uses a screenshot to freeze the screen while windows
- // resize underneath. When we are rotating seamlessly, we allow the elements to
- // transition to their rotated state independently and without a freeze required.
- prepareSeamlessRotation();
- } else {
- cancelSeamlessRotation();
- }
-
// Give a remote handler (system ui) some time to reposition things.
startRemoteRotation(oldRotation, mRotation);
@@ -677,42 +655,6 @@ public class DisplayRotation {
}
}
- /**
- * This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
- * set by previous {@link #updateRotationUnchecked}, but another orientation change happens
- * before calling {@link DisplayContent#sendNewConfiguration} (remote rotation hasn't finished)
- * and it doesn't choose seamless rotation.
- */
- void cancelSeamlessRotation() {
- if (!mRotatingSeamlessly) {
- return;
- }
- mDisplayContent.forAllWindows(w -> {
- if (w.mSeamlesslyRotated) {
- w.cancelSeamlessRotation();
- w.mSeamlesslyRotated = false;
- }
- }, true /* traverseTopToBottom */);
- mSeamlessRotationCount = 0;
- mRotatingSeamlessly = false;
- mDisplayContent.finishAsyncRotationIfPossible();
- }
-
- private void prepareSeamlessRotation() {
- // We are careful to reset this in case a window was removed before it finished
- // seamless rotation.
- mSeamlessRotationCount = 0;
- mRotatingSeamlessly = true;
- }
-
- boolean isRotatingSeamlessly() {
- return mRotatingSeamlessly;
- }
-
- boolean hasSeamlessRotatingWindow() {
- return mSeamlessRotationCount > 0;
- }
-
@VisibleForTesting
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
// Display doesn't need to be frozen because application has been started in correct
@@ -750,13 +692,6 @@ public class DisplayRotation {
return false;
}
- // We can't rotate (seamlessly or not) while waiting for the last seamless rotation to
- // complete (that is, waiting for windows to redraw). It's tempting to check
- // mSeamlessRotationCount but that could be incorrect in the case of window-removal.
- if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) {
- return false;
- }
-
return true;
}
@@ -774,28 +709,6 @@ public class DisplayRotation {
return oldRotation != Surface.ROTATION_180 && newRotation != Surface.ROTATION_180;
}
- void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
- if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
- return;
- }
-
- w.mSeamlesslyRotated = seamlesslyRotated;
- if (seamlesslyRotated) {
- mSeamlessRotationCount++;
- } else {
- mSeamlessRotationCount--;
- }
- if (mSeamlessRotationCount == 0) {
- ProtoLog.i(WM_DEBUG_ORIENTATION,
- "Performing post-rotate rotation after seamless rotation");
- // Finish seamless rotation.
- mRotatingSeamlessly = false;
- mDisplayContent.finishAsyncRotationIfPossible();
-
- updateRotationAndSendNewConfigIfChanged();
- }
- }
-
void restoreSettings(int userRotationMode, int userRotation, int fixedToUserRotation) {
mFixedToUserRotation = fixedToUserRotation;
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 8eec98cb5fcc..2406178e46fe 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -37,6 +37,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* {@link LaunchParamsController} calculates the {@link LaunchParams} by coordinating between
@@ -97,18 +98,18 @@ class LaunchParamsController {
mTmpResult.reset();
final LaunchParamsModifier modifier = mModifiers.get(i);
- switch(modifier.onCalculate(task, layout, activity, source, options, request, phase,
+ switch (modifier.onCalculate(task, layout, activity, source, options, request, phase,
mTmpCurrent, mTmpResult)) {
case RESULT_SKIP:
// Do not apply any results when we are told to skip
continue;
case RESULT_DONE:
// Set result and return immediately.
- result.set(mTmpResult);
+ result.merge(mTmpResult);
return;
case RESULT_CONTINUE:
// Set result and continue
- result.set(mTmpResult);
+ result.merge(mTmpResult);
break;
}
}
@@ -173,6 +174,7 @@ class LaunchParamsController {
*/
static class LaunchParams {
/** The bounds within the parent container. */
+ @NonNull
final Rect mBounds = new Rect();
/** The bounds within the parent container respecting insets. Usually empty. */
@NonNull
@@ -186,12 +188,17 @@ class LaunchParamsController {
@WindowingMode
int mWindowingMode;
+ /** Whether the Activity needs the safe region bounds. A {@code null} value means unset. */
+ @Nullable
+ Boolean mNeedsSafeRegionBounds = null;
+
/** Sets values back to default. {@link #isEmpty} will return {@code true} once called. */
void reset() {
mBounds.setEmpty();
mAppBounds.setEmpty();
mPreferredTaskDisplayArea = null;
mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ mNeedsSafeRegionBounds = null;
}
/** Copies the values set on the passed in {@link LaunchParams}. */
@@ -200,12 +207,26 @@ class LaunchParamsController {
mAppBounds.set(params.mAppBounds);
mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea;
mWindowingMode = params.mWindowingMode;
+ mNeedsSafeRegionBounds = params.mNeedsSafeRegionBounds;
+ }
+
+ /** Merges the values set on the passed in {@link LaunchParams}. */
+ void merge(LaunchParams params) {
+ mBounds.set(params.mBounds);
+ mAppBounds.set(params.mAppBounds);
+ mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea;
+ mWindowingMode = params.mWindowingMode;
+ // Only update mNeedsSafeRegionBounds if a modifier updates it by setting a non null
+ // value. Otherwise, carry over from previous modifiers
+ if (params.mNeedsSafeRegionBounds != null) {
+ mNeedsSafeRegionBounds = params.mNeedsSafeRegionBounds;
+ }
}
/** Returns {@code true} if no values have been explicitly set. */
boolean isEmpty() {
return mBounds.isEmpty() && mAppBounds.isEmpty() && mPreferredTaskDisplayArea == null
- && mWindowingMode == WINDOWING_MODE_UNDEFINED;
+ && mWindowingMode == WINDOWING_MODE_UNDEFINED && mNeedsSafeRegionBounds == null;
}
boolean hasWindowingMode() {
@@ -226,16 +247,19 @@ class LaunchParamsController {
if (mPreferredTaskDisplayArea != that.mPreferredTaskDisplayArea) return false;
if (mWindowingMode != that.mWindowingMode) return false;
if (!mAppBounds.equals(that.mAppBounds)) return false;
- return mBounds != null ? mBounds.equals(that.mBounds) : that.mBounds == null;
+ if (!Objects.equals(mNeedsSafeRegionBounds, that.mNeedsSafeRegionBounds)) return false;
+ return !mBounds.isEmpty() ? mBounds.equals(that.mBounds) : that.mBounds.isEmpty();
}
@Override
public int hashCode() {
- int result = mBounds != null ? mBounds.hashCode() : 0;
+ int result = !mBounds.isEmpty() ? mBounds.hashCode() : 0;
result = 31 * result + mAppBounds.hashCode();
result = 31 * result + (mPreferredTaskDisplayArea != null
? mPreferredTaskDisplayArea.hashCode() : 0);
result = 31 * result + mWindowingMode;
+ result = 31 * result + (mNeedsSafeRegionBounds != null
+ ? Boolean.hashCode(mNeedsSafeRegionBounds) : 0);
return result;
}
}
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index 6dd7d35856df..6e59828c8ff2 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -21,20 +21,13 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import android.app.PictureInPictureParams;
import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import android.util.RotationUtils;
import android.util.Slog;
import android.view.IPinnedTaskListener;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.window.PictureInPictureSurfaceTransaction;
import java.io.PrintWriter;
@@ -71,11 +64,7 @@ class PinnedTaskController {
* based on the new rotation.
*/
private Rect mDestRotatedBounds;
- /**
- * Non-null if the entering PiP task from recents animation will cause display rotation to
- * change. The transaction is based on the old rotation.
- */
- private PictureInPictureSurfaceTransaction mPipTransaction;
+
/** Whether to skip task configuration change once. */
private boolean mFreezingTaskConfig;
/** Defer display orientation change if the PiP task is animating across orientations. */
@@ -212,14 +201,12 @@ class PinnedTaskController {
}
/**
- * Sets the transaction for {@link #startSeamlessRotationIfNeeded} if the orientation of display
- * will be changed. This is only called when finishing recents animation with pending
- * orientation change that will be handled by
- * {@link DisplayContent.FixedRotationTransitionListener#onFinishRecentsAnimation}.
+ * Sets a hint if the orientation of display will be changed. This is only called when
+ * finishing recents animation with pending orientation change that will be handled by
+ * {@link DisplayContent.FixedRotationTransitionListener}.
*/
- void setEnterPipTransaction(PictureInPictureSurfaceTransaction tx) {
+ void setEnterPipWithRotatedTransientLaunch() {
mFreezingTaskConfig = true;
- mPipTransaction = tx;
}
/** Called when the activity in PiP task has PiP windowing mode (at the end of animation). */
@@ -233,81 +220,6 @@ class PinnedTaskController {
}
/**
- * Resets rotation and applies scale and position to PiP task surface to match the current
- * rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it
- * receives the callback of fixed rotation completion.
- */
- void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t,
- int oldRotation, int newRotation) {
- final Rect bounds = mDestRotatedBounds;
- final PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
- final boolean emptyPipPositionTx = pipTx == null || pipTx.mPosition == null;
- if (bounds == null && emptyPipPositionTx) {
- return;
- }
- final TaskDisplayArea taskArea = mDisplayContent.getDefaultTaskDisplayArea();
- final Task pinnedTask = taskArea.getRootPinnedTask();
- if (pinnedTask == null) {
- return;
- }
-
- mDestRotatedBounds = null;
- mPipTransaction = null;
- final Rect areaBounds = taskArea.getBounds();
- if (!emptyPipPositionTx) {
- // The transaction from recents animation is in old rotation. So the position needs to
- // be rotated.
- float dx = pipTx.mPosition.x;
- float dy = pipTx.mPosition.y;
- final Matrix matrix = pipTx.getMatrix();
- if (pipTx.mRotation == 90) {
- dx = pipTx.mPosition.y;
- dy = areaBounds.right - pipTx.mPosition.x;
- matrix.postRotate(-90);
- } else if (pipTx.mRotation == -90) {
- dx = areaBounds.bottom - pipTx.mPosition.y;
- dy = pipTx.mPosition.x;
- matrix.postRotate(90);
- }
- matrix.postTranslate(dx, dy);
- final SurfaceControl leash = pinnedTask.getSurfaceControl();
- t.setMatrix(leash, matrix, new float[9]);
- if (pipTx.hasCornerRadiusSet()) {
- t.setCornerRadius(leash, pipTx.mCornerRadius);
- }
- Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
- return;
- }
-
- final PictureInPictureParams params = pinnedTask.getPictureInPictureParams();
- final Rect sourceHintRect = params != null && params.hasSourceBoundsHint()
- ? params.getSourceRectHint()
- : null;
- Slog.i(TAG, "Seamless rotation PiP bounds=" + bounds + " hintRect=" + sourceHintRect);
- final int rotationDelta = RotationUtils.deltaRotation(oldRotation, newRotation);
- // Adjust for display cutout if applicable.
- if (sourceHintRect != null && rotationDelta == Surface.ROTATION_270) {
- if (pinnedTask.getDisplayCutoutInsets() != null) {
- final int rotationBackDelta = RotationUtils.deltaRotation(newRotation, oldRotation);
- final Rect displayCutoutInsets = RotationUtils.rotateInsets(
- Insets.of(pinnedTask.getDisplayCutoutInsets()), rotationBackDelta).toRect();
- sourceHintRect.offset(displayCutoutInsets.left, displayCutoutInsets.top);
- }
- }
- final Rect contentBounds = sourceHintRect != null && areaBounds.contains(sourceHintRect)
- ? sourceHintRect : areaBounds;
- final int w = contentBounds.width();
- final int h = contentBounds.height();
- final float scale = w <= h ? (float) bounds.width() / w : (float) bounds.height() / h;
- final int insetLeft = (int) ((contentBounds.left - areaBounds.left) * scale + .5f);
- final int insetTop = (int) ((contentBounds.top - areaBounds.top) * scale + .5f);
- final Matrix matrix = new Matrix();
- matrix.setScale(scale, scale);
- matrix.postTranslate(bounds.left - insetLeft, bounds.top - insetTop);
- t.setMatrix(pinnedTask.getSurfaceControl(), matrix, new float[9]);
- }
-
- /**
* Returns {@code true} to skip {@link Task#onConfigurationChanged} because it is expected that
* there will be a orientation change and a PiP configuration change.
*/
@@ -321,7 +233,6 @@ class PinnedTaskController {
mFreezingTaskConfig = false;
mDeferOrientationChanging = false;
mDestRotatedBounds = null;
- mPipTransaction = null;
}
/**
@@ -381,9 +292,6 @@ class PinnedTaskController {
if (mDestRotatedBounds != null) {
pw.println(prefix + " mPendingBounds=" + mDestRotatedBounds);
}
- if (mPipTransaction != null) {
- pw.println(prefix + " mPipTransaction=" + mPipTransaction);
- }
pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
pw.println(prefix + " mImeHeight=" + mImeHeight);
pw.println(prefix + " mMinAspectRatio=" + mMinAspectRatio);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 609302ce3f56..242aea941429 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -79,8 +79,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
-import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
import static java.lang.Integer.MAX_VALUE;
@@ -655,9 +653,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
final int count = mChildren.size();
for (int i = 0; i < count; ++i) {
final int pendingChanges = mChildren.get(i).pendingLayoutChanges;
- if ((pendingChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
- animator.mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING;
- }
if (pendingChanges != 0) {
hasChanges = true;
}
@@ -1024,18 +1019,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return changed;
}
- boolean copyAnimToLayoutParams() {
- boolean doRequest = false;
-
- final int bulkUpdateParams = mWmService.mAnimator.mBulkUpdateParams;
- if ((bulkUpdateParams & SET_UPDATE_ROTATION) != 0) {
- mUpdateRotation = true;
- doRequest = true;
- }
-
- return doRequest;
- }
-
private final class MyHandler extends Handler {
public MyHandler(Looper looper) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 960f5beae2b3..70dabf8d23c0 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2333,15 +2333,24 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Nullable DisplayInfo mTmpOverrideDisplayInfo;
@Nullable AppCompatDisplayInsets mTmpCompatInsets;
@Nullable Rect mParentAppBoundsOverride;
+ @Nullable Rect mParentBoundsOverride;
int mTmpOverrideConfigOrientation;
boolean mUseOverrideInsetsForConfig;
void resolveTmpOverrides(DisplayContent dc, Configuration parentConfig,
- boolean isFixedRotationTransforming) {
- mParentAppBoundsOverride = new Rect(parentConfig.windowConfiguration.getAppBounds());
+ boolean isFixedRotationTransforming, @Nullable Rect safeRegionBounds) {
+ mParentAppBoundsOverride = safeRegionBounds != null ? safeRegionBounds : new Rect(
+ parentConfig.windowConfiguration.getAppBounds());
+ mParentBoundsOverride = safeRegionBounds != null ? safeRegionBounds : new Rect(
+ parentConfig.windowConfiguration.getBounds());
mTmpOverrideConfigOrientation = parentConfig.orientation;
- final Insets insets;
- if (mUseOverrideInsetsForConfig && dc != null
+ Insets insets = Insets.NONE;
+ if (safeRegionBounds != null) {
+ // Modify orientation based on the parent app bounds if safe region bounds are set.
+ mTmpOverrideConfigOrientation =
+ mParentAppBoundsOverride.height() >= mParentAppBoundsOverride.width()
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ } else if (mUseOverrideInsetsForConfig && dc != null
&& !isFloating(parentConfig.windowConfiguration.getWindowingMode())) {
// Insets are decoupled from configuration by default from V+, use legacy
// compatibility behaviour for apps targeting SDK earlier than 35
@@ -2357,10 +2366,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
.getDecorInsetsInfo(rotation, dw, dh);
final Rect stableBounds = decorInsets.mOverrideConfigFrame;
mTmpOverrideConfigOrientation = stableBounds.width() > stableBounds.height()
- ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
insets = Insets.of(decorInsets.mOverrideNonDecorInsets);
- } else {
- insets = Insets.NONE;
}
mParentAppBoundsOverride.inset(insets);
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 803c21ccab6e..30313fc63857 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1249,7 +1249,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Skip dispatching the change for PiP task to avoid its activity drawing for the
// intermediate state which will cause flickering. The final PiP bounds in new
// rotation will be applied by PipTransition.
- ar.mDisplayContent.mPinnedTaskController.setEnterPipTransaction(null);
+ ar.mDisplayContent.mPinnedTaskController.setEnterPipWithRotatedTransientLaunch();
}
return inPip;
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 80137a298ac2..3f2b40c1d7c9 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -65,9 +65,6 @@ public class WindowAnimator {
/** Time of current animation step. Reset on each iteration */
long mCurrentTime;
- int mBulkUpdateParams = 0;
- Object mLastWindowFreezeSource;
-
private boolean mInitialized = false;
private Choreographer mChoreographer;
@@ -145,7 +142,6 @@ public class WindowAnimator {
final int animationFlags = useShellTransition ? CHILDREN : (TRANSITION | CHILDREN);
boolean rootAnimating = false;
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
- mBulkUpdateParams = 0;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
@@ -202,8 +198,7 @@ public class WindowAnimator {
}
final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this);
- final boolean doRequest = mBulkUpdateParams != 0 && root.copyAnimToLayoutParams();
- if (hasPendingLayoutChanges || doRequest) {
+ if (hasPendingLayoutChanges) {
mService.mWindowPlacerLocked.requestTraversal();
}
@@ -245,7 +240,6 @@ public class WindowAnimator {
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: exit"
- + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+ " hasPendingLayoutChanges=" + hasPendingLayoutChanges);
}
}
@@ -265,17 +259,6 @@ public class WindowAnimator {
mRunningExpensiveAnimations = runningExpensiveAnimations;
}
- private static String bulkUpdateParamsToString(int bulkUpdateParams) {
- StringBuilder builder = new StringBuilder(128);
- if ((bulkUpdateParams & WindowSurfacePlacer.SET_UPDATE_ROTATION) != 0) {
- builder.append(" UPDATE_ROTATION");
- }
- if ((bulkUpdateParams & WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING) != 0) {
- builder.append(" SET_WALLPAPER_ACTION_PENDING");
- }
- return builder.toString();
- }
-
public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) {
final String subPrefix = " " + prefix;
@@ -292,11 +275,6 @@ public class WindowAnimator {
pw.print(prefix); pw.print("mCurrentTime=");
pw.println(TimeUtils.formatUptime(mCurrentTime));
}
- if (mBulkUpdateParams != 0) {
- pw.print(prefix); pw.print("mBulkUpdateParams=0x");
- pw.print(Integer.toHexString(mBulkUpdateParams));
- pw.println(bulkUpdateParamsToString(mBulkUpdateParams));
- }
}
void scheduleAnimation() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 772a7fdfc684..5cbba355a06f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -97,6 +97,7 @@ import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import com.android.server.wm.utils.AlwaysTruePredicate;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -161,6 +162,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
protected @InsetsType int mMergedExcludeInsetsTypes = 0;
private @InsetsType int mExcludeInsetsTypes = 0;
+ /**
+ * Bounds for the safe region for this window container which control the
+ * {@link AppCompatSafeRegionPolicy}. These bounds can be passed on to the subtree if the
+ * subtree has no other bounds for the safe region. The value will be null if there are no safe
+ * region bounds for the window container.
+ */
+ @Nullable
+ private Rect mSafeRegionBounds;
+
@Nullable
private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
@@ -556,6 +566,38 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mParent != null ? mParent.mMergedExcludeInsetsTypes : 0);
}
+ /**
+ * Returns the safe region bounds on the window container. If the window container has no safe
+ * region bounds set, the safe region bounds as set on the nearest ancestor is returned.
+ */
+ @Nullable
+ Rect getSafeRegionBounds() {
+ if (mSafeRegionBounds != null) {
+ return mSafeRegionBounds;
+ }
+ if (mParent == null) {
+ return null;
+ }
+ return mParent.getSafeRegionBounds();
+ }
+
+ /**
+ * Sets the safe region bounds on the window container. Set bounds to {@code null} to reset.
+ *
+ * @param safeRegionBounds the safe region {@link Rect} that should be set on this
+ * WindowContainer
+ */
+ void setSafeRegionBounds(@Nullable Rect safeRegionBounds) {
+ if (!Flags.safeRegionLetterboxing()) {
+ Slog.i(TAG, "Feature safe region letterboxing is not available");
+ return;
+ }
+ mSafeRegionBounds = safeRegionBounds;
+ // Trigger a config change whenever this method is called since the safe region bounds
+ // can be modified (including a reset).
+ onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+ }
+
private void mergeExcludeInsetsTypesAndNotifyInsetsChanged(
@InsetsType int excludeInsetsTypesFromParent) {
final ArraySet<WindowState> changedWindows = new ArraySet<>();
@@ -3230,6 +3272,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mLocalInsetsSources.valueAt(i).dump(childPrefix, pw);
}
}
+ pw.println(prefix + mSafeRegionBounds + " SafeRegionBounds");
}
final void updateSurfacePositionNonOrganized() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c078d67b6cc6..ff43d72c5a07 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -198,6 +198,8 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.configstore.V1_0.OptionalBool;
import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
+import android.hardware.devicestate.DeviceState;
+import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputSettings;
@@ -1032,6 +1034,21 @@ public class WindowManagerService extends IWindowManager.Stub
PowerManager mPowerManager;
PowerManagerInternal mPowerManagerInternal;
+ private DeviceStateManager mDeviceStateManager;
+ private DeviceStateCallback mDeviceStateCallback;
+ private class DeviceStateCallback implements DeviceStateManager.DeviceStateCallback {
+ private DeviceState mCurrentDeviceState;
+ @Override
+ public void onDeviceStateChanged(@NonNull DeviceState state) {
+ mCurrentDeviceState = state;
+ }
+
+ boolean isInRearDisplayOuterDefaultState() {
+ return mCurrentDeviceState != null && mCurrentDeviceState
+ .hasProperties(DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT);
+ }
+ }
+
private float mWindowAnimationScaleSetting = 1.0f;
private float mTransitionAnimationScaleSetting = 1.0f;
private float mAnimatorDurationScaleSetting = 1.0f;
@@ -1317,6 +1334,10 @@ public class WindowManagerService extends IWindowManager.Stub
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+ mDeviceStateCallback = new DeviceStateCallback();
+ mDeviceStateManager.registerCallback(new HandlerExecutor(mH), mDeviceStateCallback);
+
if (mPowerManagerInternal != null) {
mPowerManagerInternal.registerLowPowerModeObserver(
new PowerManagerInternal.LowPowerModeListener() {
@@ -2132,7 +2153,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
final DisplayContent dc = win.getDisplayContent();
- dc.getDisplayRotation().markForSeamlessRotation(win, false /* seamlesslyRotated */);
win.resetAppOpsState();
@@ -8950,6 +8970,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ if (mDeviceStateCallback.isInRearDisplayOuterDefaultState()) {
+ final Display[] rearDisplays = mDisplayManager
+ .getDisplays(DisplayManager.DISPLAY_CATEGORY_REAR);
+ if (rearDisplays.length > 0 && rearDisplays[0].getDisplayId() == t.getDisplayId()) {
+ // Do not change display focus to the inner display if we're in this mode. Note that
+ // in this mode, the inner display is configured as a rear display.
+ Slog.w(TAG, "Ignoring focus change because device is in RDM.");
+ return;
+ }
+ }
+
clearPointerDownOutsideFocusRunnable();
final InputTarget focusedInputTarget = mFocusedInputTarget;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a012ec137892..c19fa8c03e0a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -78,6 +78,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
import static android.window.WindowContainerTransaction.HierarchyOp.REACHABILITY_EVENT_X;
import static android.window.WindowContainerTransaction.HierarchyOp.REACHABILITY_EVENT_Y;
@@ -472,35 +473,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
transition.setAllReady();
}
- // TODO(b/365884835): remove this method and callers.
- @Override
- public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
- @NonNull IWindowContainerTransactionCallback callback,
- @NonNull WindowContainerTransaction t) {
- enforceTaskPermission("startLegacyTransition()");
- final CallerInfo caller = new CallerInfo();
- final long ident = Binder.clearCallingIdentity();
- int syncId;
- try {
- synchronized (mGlobalLock) {
- if (type < 0) {
- throw new IllegalArgumentException("Can't create transition with no type");
- }
- if (mTransitionController.getTransitionPlayer() != null) {
- throw new IllegalArgumentException("Can't use legacy transitions in"
- + " when shell transitions are enabled.");
- }
- syncId = startSyncWithOrganizer(callback);
- applyTransaction(t, syncId, mService.mChainTracker.startLegacy("legacyTransit"),
- caller);
- setSyncReady(syncId);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return syncId;
- }
-
@Override
public void finishTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t) {
@@ -1544,6 +1516,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
container.setExcludeInsetsTypes(hop.getExcludeInsetsTypes());
break;
}
+ case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ if (container == null || !container.isAttached()) {
+ Slog.e(TAG,
+ "Attempt to operate on unknown or detached container: " + container);
+ break;
+ }
+ if (chain.mTransition != null) {
+ chain.mTransition.collect(container);
+ }
+ container.setSafeRegionBounds(hop.getSafeRegionBounds());
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
}
return effects;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d528776a2c25..a270af56cbcd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -169,7 +169,6 @@ import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY;
import static com.android.server.wm.WindowStateProto.IS_VISIBLE;
import static com.android.server.wm.WindowStateProto.KEEP_CLEAR_AREAS;
import static com.android.server.wm.WindowStateProto.MERGED_LOCAL_INSETS_SOURCES;
-import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.REMOVED;
import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
@@ -231,7 +230,6 @@ import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.Surface;
-import android.view.Surface.Rotation;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewDebug;
@@ -399,7 +397,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* rotation.
*/
final boolean mForceSeamlesslyRotate;
- SeamlessRotator mPendingSeamlessRotate;
private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
@@ -593,13 +590,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/** The time when the window was last requested to redraw for orientation change. */
private long mOrientationChangeRedrawRequestTime;
- /**
- * The orientation during the last visible call to relayout. If our
- * current orientation is different, the window can't be ready
- * to be shown.
- */
- int mLastVisibleLayoutRotation = -1;
-
/** Is this window now (or just being) removed? */
boolean mRemoved;
@@ -655,15 +645,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean mIsSurfacePositionPaused;
/**
- * During seamless rotation we have two phases, first the old window contents
- * are rotated to look as if they didn't move in the new coordinate system. Then we
- * have to freeze updates to this layer (to preserve the transformation) until
- * the resize actually occurs. This is true from when the transformation is set
- * and false until the transaction to resize is sent.
- */
- boolean mSeamlesslyRotated = false;
-
- /**
* Whether the IME insets have been consumed. If {@code true}, this window won't be able to
* receive visible IME insets; {@code false}, otherwise.
*/
@@ -778,11 +759,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
private boolean mInsetsAnimationRunning;
- private final Consumer<SurfaceControl.Transaction> mSeamlessRotationFinishedConsumer = t -> {
- finishSeamlessRotation(t);
- updateSurfacePosition(t);
- };
-
private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
// Only apply the position to the surface when there's no leash created.
if (mSurfaceControl != null && mSurfaceControl.isValid() && !mSurfaceAnimator.hasLeash()) {
@@ -899,69 +875,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return visible && mFrozenInsetsState == null;
}
- void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
- @Rotation int rotation, boolean requested) {
- // Invisible windows and the wallpaper do not participate in the seamless rotation animation
- if (!isVisibleNow() || mIsWallpaper) {
- return;
- }
-
- if (mToken.hasFixedRotationTransform()) {
- // The transform of its surface is handled by fixed rotation.
- return;
- }
- final Task task = getTask();
- if (task != null && task.inPinnedWindowingMode()) {
- // It is handled by PinnedTaskController. Note that the windowing mode of activity
- // and windows may still be fullscreen.
- return;
- }
-
- if (mPendingSeamlessRotate != null) {
- oldRotation = mPendingSeamlessRotate.getOldRotation();
- }
-
- // Skip performing seamless rotation when the controlled insets is IME with visible state.
- if (mControllableInsetProvider != null
- && mControllableInsetProvider.getSource().getType() == WindowInsets.Type.ime()) {
- return;
- }
-
- if (mForceSeamlesslyRotate || requested) {
- if (mControllableInsetProvider != null) {
- mControllableInsetProvider.startSeamlessRotation();
- }
- mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo(),
- false /* applyFixedTransformationHint */);
- // The surface position is going to be unrotated according to the last position.
- // Make sure the source position is up-to-date.
- mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
- mPendingSeamlessRotate.unrotate(transaction, this);
- getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
- true /* seamlesslyRotated */);
- applyWithNextDraw(mSeamlessRotationFinishedConsumer);
- }
- }
-
- void cancelSeamlessRotation() {
- finishSeamlessRotation(getPendingTransaction());
- }
-
- void finishSeamlessRotation(SurfaceControl.Transaction t) {
- if (mPendingSeamlessRotate == null) {
- return;
- }
-
- mPendingSeamlessRotate.finish(t, this);
- mPendingSeamlessRotate = null;
-
- getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
- false /* seamlesslyRotated */);
- if (mControllableInsetProvider != null) {
- mControllableInsetProvider.finishSeamlessRotation();
- }
- }
-
List<Rect> getSystemGestureExclusion() {
return mExclusionRects;
}
@@ -2176,8 +2089,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
&& (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
&& !isDragResizing()
&& hasMovementAnimation
- && !mWinAnimator.mLastHidden
- && !mSeamlesslyRotated;
+ && !mWinAnimator.mLastHidden;
}
/**
@@ -3859,14 +3771,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
- * Returns {@code true} if activity bounds are letterboxed or letterboxed for display cutout.
+ * Returns {@code true} if activity bounds are letterboxed or letterboxed for display cutout or
+ * letterboxed for a safe region.
*
* <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
- * AppCompatLetterboxOverrides#shouldShowLetterboxUi} for more context.
+ * AppCompatLetterboxPolicy#shouldShowLetterboxUi} for more context.
*/
boolean areAppWindowBoundsLetterboxed() {
return mActivityRecord != null && !isStartingWindowAssociatedToTask()
- && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
+ && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout()
+ || mActivityRecord.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
}
/** Returns {@code true} if the window is letterboxed for the display cutout. */
@@ -3995,7 +3910,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
proto.write(REMOVED, mRemoved);
proto.write(IS_ON_SCREEN, isOnScreen());
proto.write(IS_VISIBLE, isVisible);
- proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null);
proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
proto.write(HAS_COMPAT_SCALE, hasCompatScale());
proto.write(GLOBAL_SCALE, mGlobalScale);
@@ -4141,14 +4055,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
- pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate
- + " seamlesslyRotate: pending=");
- if (mPendingSeamlessRotate != null) {
- mPendingSeamlessRotate.dump(pw);
- } else {
- pw.print("null");
- }
- pw.println();
if (mXOffset != 0 || mYOffset != 0) {
pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset);
@@ -4880,8 +4786,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mWinAnimator.mEnterAnimationPending = true;
}
- mLastVisibleLayoutRotation = getDisplayContent().getRotation();
-
mWinAnimator.mEnteringAnimation = true;
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareToDisplay");
@@ -5279,9 +5183,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
- if ((asyncRotationController != null
- && asyncRotationController.hasSeamlessOperation(mToken))
- || mPendingSeamlessRotate != null) {
+ if (asyncRotationController != null
+ && asyncRotationController.hasSeamlessOperation(mToken)) {
// Freeze position while un-rotating the window, so its surface remains at the position
// corresponding to the original rotation.
return;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index a34b5115faf9..4fb74ef00914 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -42,9 +42,6 @@ class WindowSurfacePlacer {
/** Only do a maximum of 6 repeated layouts. After that quit */
private int mLayoutRepeatCount;
- static final int SET_UPDATE_ROTATION = 1 << 0;
- static final int SET_WALLPAPER_ACTION_PENDING = 1 << 1;
-
private boolean mTraversalScheduled;
private int mDeferDepth = 0;
/** The number of layout requests when deferring. */
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index e32ce525cb40..ec8794f8073f 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -671,9 +671,13 @@ void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph)
return;
}
- // TODO(b/383092013): Add topology validation
const DisplayTopologyGraph displayTopology =
android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+ if (input_flags::enable_display_topology_validation() && !displayTopology.isValid()) {
+ LOG(ERROR) << "Ignoring Invalid DisplayTopology";
+ return;
+ }
+
mInputManager->getDispatcher().setDisplayTopology(displayTopology);
mInputManager->getChoreographer().setDisplayTopology(displayTopology);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
index 29f07227a12d..fecbc7c81347 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -25,7 +25,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.hardware.Sensor;
@@ -396,7 +396,7 @@ public final class DisplayPowerProximityStateControllerTest {
assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
- verifyZeroInteractions(mWakelockController);
+ verifyNoMoreInteractions(mWakelockController);
}
private void setScreenOffBecauseOfPositiveProximityState() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
index 019b70ef1424..f067fa10f611 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.hardware.display.DisplayManagerInternal;
@@ -210,7 +210,7 @@ public final class WakelockControllerTest {
// Validate one suspend blocker was released
assertFalse(mWakelockController.isProximityPositiveAcquired());
- verifyZeroInteractions(mDisplayPowerCallbacks);
+ verifyNoMoreInteractions(mDisplayPowerCallbacks);
}
@Test
@@ -238,7 +238,7 @@ public final class WakelockControllerTest {
// Validate one suspend blocker was released
assertFalse(mWakelockController.isProximityNegativeAcquired());
- verifyZeroInteractions(mDisplayPowerCallbacks);
+ verifyNoMoreInteractions(mDisplayPowerCallbacks);
}
@Test
@@ -265,7 +265,7 @@ public final class WakelockControllerTest {
// Validate one suspend blocker was released
assertFalse(mWakelockController.isOnStateChangedPending());
- verifyZeroInteractions(mDisplayPowerCallbacks);
+ verifyNoMoreInteractions(mDisplayPowerCallbacks);
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 49de80179683..bf0543939d85 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -422,7 +421,7 @@ public final class DisplayBrightnessControllerTest {
mDisplayBrightnessController.setAutomaticBrightnessController(
automaticBrightnessController);
assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
- verifyZeroInteractions(automaticBrightnessController);
+ verifyNoMoreInteractions(automaticBrightnessController);
verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index eda5e8613dba..77d67019c0ed 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -67,6 +67,9 @@ import java.util.concurrent.TimeUnit;
/**
* Test RescueParty.
+ * TODO: b/354112511 delete this file
+ * Moved to frameworks/base/tests/PackageWatchdog/src/com/android/server/RescuePartyTest
+ *
*/
public class RescuePartyTest {
private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index d79d88400cf9..f0e61ec9c692 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -109,7 +109,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.Manifest;
import android.app.ActivityManager;
@@ -3724,8 +3724,8 @@ public final class AlarmManagerServiceTest {
setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, 0);
mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
- verifyZeroInteractions(mPackageManagerInternal);
- verifyZeroInteractions(mService.mHandler);
+ verifyNoMoreInteractions(mPackageManagerInternal);
+ verifyNoMoreInteractions(mService.mHandler);
}
private void testTemporaryQuota_bumpedAfterDeferral(int standbyBucket) throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
index 7dab1c854625..859d2d2f2e38 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
@@ -30,7 +30,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -244,7 +244,7 @@ public class AlarmStoreTest {
addAlarmsToStore(simpleAlarm, alarmClock);
mAlarmStore.remove(simpleAlarm::equals);
- verifyZeroInteractions(onRemoved);
+ verifyNoMoreInteractions(onRemoved);
mAlarmStore.remove(alarmClock::equals);
verify(onRemoved).run();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 35ab2d233563..acc06d0c7cba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -74,7 +74,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.Manifest;
import android.app.ActivityManager;
@@ -584,7 +583,7 @@ public class ActivityManagerServiceTest {
if (app.uid == uidRec.getUid() && expectedBlockState == NETWORK_STATE_BLOCK) {
verify(app.getThread()).setNetworkBlockSeq(uidRec.curProcStateSeq);
} else {
- verifyZeroInteractions(app.getThread());
+ verifyNoMoreInteractions(app.getThread());
}
Mockito.reset(app.getThread());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
index ae0452a60161..b7087c74bf8d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
@@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
@@ -105,7 +105,7 @@ public class FullBackupUtilsTest {
} catch (EOFException expected) {
}
- verifyZeroInteractions(mOutputStreamMock);
+ verifyNoMoreInteractions(mOutputStreamMock);
assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
}
@@ -126,7 +126,7 @@ public class FullBackupUtilsTest {
} catch (EOFException expected) {
}
- verifyZeroInteractions(mOutputStreamMock);
+ verifyNoMoreInteractions(mOutputStreamMock);
assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
}
@@ -141,7 +141,7 @@ public class FullBackupUtilsTest {
FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor, mOutputStreamMock);
- verifyZeroInteractions(mOutputStreamMock);
+ verifyNoMoreInteractions(mOutputStreamMock);
assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index bf7e3a0bd0a6..346d5f787621 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -32,7 +32,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import android.app.backup.IBackupManagerMonitor;
@@ -239,7 +238,7 @@ public class TarBackupReaderTest {
mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
- verifyZeroInteractions(mBackupManagerMonitorMock);
+ verifyNoMoreInteractions(mBackupManagerMonitorMock);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 3e8794377d37..2d84887afb41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -1029,6 +1029,7 @@ public class QuotaControllerTest {
@Test
@EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(0, "com.android.test",
@@ -1127,6 +1128,54 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes_Tuning() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Exempted
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED;
+ expectedStats.expirationTimeElapsed = now + 34 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 5;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 1;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ EXEMPTED_INDEX));
+ }
+
+ // Active
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ // There is only one session in the past active bucket window, the empty time for this
+ // window is the bucket window size - duration of the session.
+ expectedStats.expirationTimeElapsed = now + 54 * MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
*/
@@ -1195,6 +1244,7 @@ public class QuotaControllerTest {
@Test
@EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes() {
// Set time to 3 minutes after boot.
advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
@@ -1206,10 +1256,10 @@ public class QuotaControllerTest {
ExecutionStats expectedStats = new ExecutionStats();
// Exempted
- expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
- expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
- expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED;
expectedStats.expirationTimeElapsed = 10 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 2;
@@ -1268,6 +1318,49 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes_Tunning() {
+ // Set time to 3 minutes after boot.
+ advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
+ advanceElapsedClock(3 * MINUTE_IN_MILLIS);
+
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), false);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Exempted
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED;
+ expectedStats.expirationTimeElapsed = 30 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 2;
+ expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 2;
+ expectedStats.sessionCountInWindow = 1;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ EXEMPTED_INDEX));
+ }
+
+ // Active
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.expirationTimeElapsed = 50 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
*/
@@ -1425,6 +1518,7 @@ public class QuotaControllerTest {
@Test
@EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes() {
for (int i = 0; i < 20; ++i) {
mQuotaController.saveTimingSession(0, "com.android.test",
@@ -1581,6 +1675,165 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes_Tuning() {
+ for (int i = 0; i < 20; ++i) {
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ 5 * MINUTE_IN_MILLIS, 5), false);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ for (int j = 0; j < 5; ++j) {
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ MINUTE_IN_MILLIS, 2), false);
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ advanceElapsedClock(54 * SECOND_IN_MILLIS);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false);
+ advanceElapsedClock(500);
+ advanceElapsedClock(400);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false);
+ advanceElapsedClock(100);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ }
+ advanceElapsedClock(40 * MINUTE_IN_MILLIS);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(16, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(64, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(192, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(320, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(11, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_WORKING_MS * 5 TimingSessions are coalesced
+ assertEquals(44, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_FREQUENT_MS * 5 TimingSessions are coalesced
+ assertEquals(132, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_RARE_MS * 5 TimingSessions are coalesced
+ assertEquals(220, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(11, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(44, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(132, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(220, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * SECOND_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(7, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_WORKING_MS * 9 TimingSessions are coalesced
+ assertEquals(28, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_FREQUENT_MS * 9 TimingSessions are coalesced
+ assertEquals(84, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_RARE_MS * 9 TimingSessions are coalesced
+ assertEquals(140, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ MINUTE_IN_MILLIS);
+
+ // Only two TimingSessions there for every hour.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(2, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(8, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(24, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(40, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * MINUTE_IN_MILLIS);
+
+ // Only one TimingSessions there for every hour
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(1, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 15 * MINUTE_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(1, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference
+ // between an hour and 15 minutes.
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(1, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
*/
@@ -2231,32 +2484,6 @@ public class QuotaControllerTest {
}
}
- @Test
- @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
- public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_NewDefaultBucketWindowSizes() {
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false);
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
- false);
-
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
- 20 * MINUTE_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS);
- // window size = allowed time, so jobs can essentially run non-stop until they reach the
- // max execution time.
- setStandbyBucket(EXEMPTED_INDEX);
- synchronized (mQuotaController.mLock) {
- assertEquals(10 * MINUTE_IN_MILLIS,
- mQuotaController.getRemainingExecutionTimeLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS,
- mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
- }
- }
-
/**
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
* window.
@@ -2327,6 +2554,7 @@ public class QuotaControllerTest {
@Test
@EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Close to RARE boundary.
@@ -2390,7 +2618,30 @@ public class QuotaControllerTest {
@Test
@EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes_Tuning() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Close to ACTIVE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+
+ // ACTIVE window != allowed time.
+ setStandbyBucket(ACTIVE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(17 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(20 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER})
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetTimeUntilQuotaConsumedLocked_Installer() {
PackageInfo pi = new PackageInfo();
pi.packageName = SOURCE_PACKAGE;
@@ -2412,7 +2663,7 @@ public class QuotaControllerTest {
// Far away from FREQUENT boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
+ now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
2 * MINUTE_IN_MILLIS, 5), false);
// Overlap WORKING_SET boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2422,12 +2673,12 @@ public class QuotaControllerTest {
// Close to ACTIVE boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
+ now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
2 * MINUTE_IN_MILLIS, 5), false);
// Close to EXEMPTED boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS),
+ now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS),
2 * MINUTE_IN_MILLIS, 5), false);
// No additional quota for the system installer when the app is in RARE, FREQUENT,
@@ -2486,6 +2737,42 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetTimeUntilQuotaConsumedLocked_Installer_Tuning() {
+ PackageInfo pi = new PackageInfo();
+ pi.packageName = SOURCE_PACKAGE;
+ pi.requestedPermissions = new String[]{Manifest.permission.INSTALL_PACKAGES};
+ pi.requestedPermissionsFlags = new int[]{PackageInfo.REQUESTED_PERMISSION_GRANTED};
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.uid = mSourceUid;
+ doReturn(List.of(pi)).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission(
+ eq(Manifest.permission.INSTALL_PACKAGES), anyInt(), eq(mSourceUid));
+ mQuotaController.onSystemServicesReady();
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Close to EXEMPTED boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS),
+ 2 * MINUTE_IN_MILLIS, 5), false);
+
+ // Additional quota for the system installer when the app is in EXEMPTED bucket.
+ // EXEMPTED window == allowed time.
+ setStandbyBucket(EXEMPTED_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(38 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 2 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
*/
@@ -2637,33 +2924,6 @@ public class QuotaControllerTest {
}
}
- @Test
- @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
- public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_NewDefaultBucketWindowSizes() {
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (24 * HOUR_IN_MILLIS),
- mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS, 5),
- false);
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (20 * MINUTE_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5),
- false);
-
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
- 20 * MINUTE_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS);
- // window size != allowed time.
- setStandbyBucket(EXEMPTED_INDEX);
- synchronized (mQuotaController.mLock) {
- assertEquals(0,
- mQuotaController.getRemainingExecutionTimeLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS,
- mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
- }
- }
-
/**
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
* window and the session is rolling out of the window.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index 3d0c63780ef3..29af7d28339d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -27,7 +27,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.AssertJUnit.assertEquals;
@@ -128,7 +128,7 @@ public class BackgroundUserSoundNotifierTest {
AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
clearInvocations(mNotificationManager);
mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi);
- verifyZeroInteractions(mNotificationManager);
+ verifyNoMoreInteractions(mNotificationManager);
}
@Test
@@ -143,7 +143,7 @@ public class BackgroundUserSoundNotifierTest {
Build.VERSION.SDK_INT);
clearInvocations(mNotificationManager);
mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi);
- verifyZeroInteractions(mNotificationManager);
+ verifyNoMoreInteractions(mNotificationManager);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
index 8257168f8d08..8257168f8d08 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
index dc04b6aea318..bf3fe8c70bf1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
@@ -29,7 +29,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AppOpsManager;
import android.content.Context;
@@ -176,9 +176,9 @@ public class CameraPrivacyLightControllerTest {
prepareCameraPrivacyLightController(List.of(getNextLight(true)), Collections.EMPTY_SET,
true, new int[0], mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis);
- verifyZeroInteractions(mLightsManager);
- verifyZeroInteractions(mAppOpsManager);
- verifyZeroInteractions(mSensorManager);
+ verifyNoMoreInteractions(mLightsManager);
+ verifyNoMoreInteractions(mAppOpsManager);
+ verifyNoMoreInteractions(mSensorManager);
}
@Test
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index 4e56422ec391..b6f4e13cad8c 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -39,7 +39,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManagerInternal;
@@ -205,7 +204,7 @@ public class NotifierTest {
mTestExecutor.simulateAsyncExecutionOfLastCommand();
// THEN the device doesn't vibrate
- verifyZeroInteractions(mVibrator);
+ verifyNoMoreInteractions(mVibrator);
}
@Test
@@ -238,7 +237,7 @@ public class NotifierTest {
mTestExecutor.simulateAsyncExecutionOfLastCommand();
// THEN the device doesn't vibrate
- verifyZeroInteractions(mVibrator);
+ verifyNoMoreInteractions(mVibrator);
}
@Test
@@ -728,7 +727,7 @@ public class NotifierTest {
mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
"my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
exceptingCallback);
- verifyZeroInteractions(mWakeLockLog);
+ verifyNoMoreInteractions(mWakeLockLog);
mTestLooper.dispatchAll();
verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
clearInvocations(mBatteryStats);
@@ -845,7 +844,7 @@ public class NotifierTest {
exceptingCallback);
// No interaction because we expect that to happen in async
- verifyZeroInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
+ verifyNoMoreInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
// Progressing the looper, and validating all the interactions
mTestLooper.dispatchAll();
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index 879aa4893802..2fd316edf71a 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -409,7 +409,7 @@ public class IntrusionDetectionServiceTest {
final String TAG = "startTestService";
final CountDownLatch latch = new CountDownLatch(1);
LocalIntrusionDetectionEventTransport transport =
- new LocalIntrusionDetectionEventTransport();
+ new LocalIntrusionDetectionEventTransport(mContext);
ServiceConnection serviceConnection = new ServiceConnection() {
// Called when connection with the service is established.
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
index f0012da44fa4..b0b781575cb3 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
@@ -18,8 +18,13 @@
package com.android.coretests.apps.testapp;
+import android.app.admin.SecurityLog;
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.Context;
+import android.content.Intent;
import android.security.intrusiondetection.IntrusionDetectionEvent;
import android.security.intrusiondetection.IntrusionDetectionEventTransport;
+import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -36,6 +41,44 @@ import java.util.List;
public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEventTransport {
private List<IntrusionDetectionEvent> mEvents = new ArrayList<>();
+ private static final String ACTION_SECURITY_EVENT_RECEIVED =
+ "com.android.coretests.apps.testapp.ACTION_SECURITY_EVENT_RECEIVED";
+ private static final String TAG = "LocalIntrusionDetectionEventTransport";
+ private static final String TEST_SECURITY_EVENT_TAG = "test_security_event_tag";
+ private static Context sContext;
+
+ public LocalIntrusionDetectionEventTransport(Context context) {
+ sContext = context;
+ }
+
+ // Broadcast an intent to the CTS test service to indicate that the security
+ // event was received.
+ private static void broadcastSecurityEventReceived() {
+ try {
+ Intent intent = new Intent(ACTION_SECURITY_EVENT_RECEIVED);
+ sContext.sendBroadcast(intent);
+ Log.i(TAG, "LIZ_TESTING: sent broadcast");
+ } catch (Exception e) {
+ Log.e(TAG, "Exception sending broadcast", e);
+ }
+ }
+
+ private static void checkIfSecurityEventReceivedFromCts(List<IntrusionDetectionEvent> events) {
+ // Loop through the events and check if any of them are the security event
+ // that uses the TEST_SECURITY_EVENT_TAG tag, which is set by the CTS test.
+ for (IntrusionDetectionEvent event : events) {
+ if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) {
+ SecurityEvent securityEvent = event.getSecurityEvent();
+ Object[] eventData = (Object[]) securityEvent.getData();
+ if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED
+ && eventData[1].equals(TEST_SECURITY_EVENT_TAG)) {
+ broadcastSecurityEventReceived();
+ return;
+ }
+ }
+ }
+ }
+
@Override
public boolean initialize() {
return true;
@@ -43,6 +86,11 @@ public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEve
@Override
public boolean addData(List<IntrusionDetectionEvent> events) {
+ // Our CTS tests will generate a security event. In order to
+ // verify the event is received with the appropriate data, we will
+ // check the events locally and set a property value that can be
+ // read by the test.
+ checkIfSecurityEventReceivedFromCts(events);
mEvents.addAll(events);
return true;
}
@@ -55,4 +103,4 @@ public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEve
public List<IntrusionDetectionEvent> getEvents() {
return mEvents;
}
-} \ No newline at end of file
+}
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
index e4bf987402fd..9183a75580ff 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
@@ -17,19 +17,20 @@
package com.android.coretests.apps.testapp;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
-import android.os.Process;
-
-import com.android.internal.infra.AndroidFuture;
-
public class TestLoggingService extends Service {
private static final String TAG = "TestLoggingService";
private LocalIntrusionDetectionEventTransport mLocalIntrusionDetectionEventTransport;
- public TestLoggingService() {
- mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport();
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Context context = getApplicationContext();
+ mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport(context);
}
// Binder given to clients.
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 75df9a8707c0..d254e9689048 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -35,7 +35,6 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.Manifest.permission;
@@ -243,7 +242,7 @@ public class NetworkScoreServiceTest {
@Test
public void testRequestScores_providerNotConnected() throws Exception {
assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
- verifyZeroInteractions(mRecommendationProvider);
+ verifyNoMoreInteractions(mRecommendationProvider);
}
@Test
@@ -604,7 +603,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, null /*cookie*/);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
- verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -618,7 +617,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
- verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -632,7 +631,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, -1 /*cookie*/);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
- verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -646,7 +645,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, "not an int" /*cookie*/);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
- verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -658,7 +657,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
- verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -676,7 +675,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
verify(mNetworkScoreCache).updateScores(filteredList);
- verifyZeroInteractions(mScanResultsFilter);
+ verifyNoMoreInteractions(mScanResultsFilter);
}
@Test
@@ -694,7 +693,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
verify(mNetworkScoreCache).updateScores(filteredList);
- verifyZeroInteractions(mCurrentNetworkFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 42b84bdc51e6..c7c8c5846bb1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -64,7 +64,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityService;
@@ -838,7 +837,7 @@ public class AbstractAccessibilityServiceConnectionTest {
// ...without secure layers included
assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isFalse();
// No error sent to callback
- verifyZeroInteractions(mMockCallback);
+ verifyNoMoreInteractions(mMockCallback);
}
@Test
@@ -856,7 +855,7 @@ public class AbstractAccessibilityServiceConnectionTest {
// ...with secure layers included
assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue();
// No error sent to callback
- verifyZeroInteractions(mMockCallback);
+ verifyNoMoreInteractions(mMockCallback);
}
@Test
@@ -889,7 +888,7 @@ public class AbstractAccessibilityServiceConnectionTest {
// ...with secure layers included
assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue();
// No error sent to callback
- verifyZeroInteractions(mMockCallback);
+ verifyNoMoreInteractions(mMockCallback);
}
private void takeScreenshotOfWindow(int windowFlags) throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 8253595a50d1..2ccd33648c3e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -50,6 +50,7 @@ import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
@@ -66,6 +67,7 @@ import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -77,10 +79,12 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.input.KeyGestureEvent;
import android.net.Uri;
@@ -114,6 +118,7 @@ import android.view.accessibility.IUserInitializationCompleteCallback;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.TestUtils;
import com.android.internal.R;
@@ -136,6 +141,8 @@ import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import com.google.common.truth.Correspondence;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -188,6 +195,8 @@ public class AccessibilityManagerServiceTest {
DESCRIPTION,
TEST_PENDING_INTENT);
+ private static final int FAKE_SYSTEMUI_UID = 1000;
+
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
private static final String TARGET_MAGNIFICATION = MAGNIFICATION_CONTROLLER_NAME;
private static final ComponentName TARGET_ALWAYS_ON_A11Y_SERVICE =
@@ -207,11 +216,12 @@ public class AccessibilityManagerServiceTest {
@Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController;
@Mock private PackageManager mMockPackageManager;
+ @Mock
+ private PackageManagerInternal mMockPackageManagerInternal;
@Mock private WindowManagerInternal mMockWindowManagerService;
@Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
@Mock private SystemActionPerformer mMockSystemActionPerformer;
@Mock private AccessibilityWindowManager mMockA11yWindowManager;
- @Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
@Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
@Mock private UserManagerInternal mMockUserManagerInternal;
@Mock private IBinder mMockBinder;
@@ -234,6 +244,7 @@ public class AccessibilityManagerServiceTest {
private TestableLooper mTestableLooper;
private Handler mHandler;
private FakePermissionEnforcer mFakePermissionEnforcer;
+ private TestDisplayManagerWrapper mTestDisplayManagerWrapper;
@Before
public void setUp() throws Exception {
@@ -246,6 +257,7 @@ public class AccessibilityManagerServiceTest {
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
LocalServices.removeServiceForTest(PermissionEnforcer.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(
WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
@@ -256,6 +268,12 @@ public class AccessibilityManagerServiceTest {
mInputFilter = mock(FakeInputFilter.class);
mTestableContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
+ when(mMockPackageManagerInternal.getSystemUiServiceComponent()).thenReturn(
+ new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService"));
+ when(mMockPackageManagerInternal.getPackageUid(eq("com.android.systemui"), anyLong(),
+ anyInt())).thenReturn(FAKE_SYSTEMUI_UID);
+ LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+
when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
mMockMagnificationConnectionManager);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
@@ -273,15 +291,9 @@ public class AccessibilityManagerServiceTest {
eq(UserHandle.USER_CURRENT)))
.thenReturn(mTestableContext.getUserId());
- final ArrayList<Display> displays = new ArrayList<>();
- final Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(),
- Display.DEFAULT_DISPLAY, new DisplayInfo(),
- DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
- final Display testDisplay = new Display(DisplayManagerGlobal.getInstance(), TEST_DISPLAY,
- new DisplayInfo(), DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
- displays.add(defaultDisplay);
- displays.add(testDisplay);
- when(mMockA11yDisplayListener.getValidDisplayList()).thenReturn(displays);
+ mTestDisplayManagerWrapper = new TestDisplayManagerWrapper(mTestableContext);
+ mTestDisplayManagerWrapper.mDisplays = createFakeDisplayList(Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL);
mA11yms = new AccessibilityManagerService(
mTestableContext,
@@ -290,7 +302,7 @@ public class AccessibilityManagerServiceTest {
mMockSecurityPolicy,
mMockSystemActionPerformer,
mMockA11yWindowManager,
- mMockA11yDisplayListener,
+ mTestDisplayManagerWrapper,
mMockMagnificationController,
mInputFilter,
mProxyManager,
@@ -2309,6 +2321,73 @@ public class AccessibilityManagerServiceTest {
mA11yms.getCurrentUserIdLocked())).isEmpty();
}
+ @Test
+ public void displayListReturnsDisplays() {
+ mTestDisplayManagerWrapper.mDisplays = createFakeDisplayList(
+ Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL,
+ Display.TYPE_WIFI,
+ Display.TYPE_OVERLAY,
+ Display.TYPE_VIRTUAL
+ );
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // In #setUp() we already have TYPE_INTERNAL and TYPE_EXTERNAL. Call the rest.
+ for (int i = 2; i < mTestDisplayManagerWrapper.mDisplays.size(); i++) {
+ mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(
+ mTestDisplayManagerWrapper.mDisplays.get(i).getDisplayId());
+ }
+ });
+
+ List<Display> displays = mA11yms.getValidDisplayList();
+ assertThat(displays).hasSize(5);
+ assertThat(displays)
+ .comparingElementsUsing(
+ Correspondence.transforming(Display::getType, "has a type of"))
+ .containsExactly(Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL,
+ Display.TYPE_WIFI,
+ Display.TYPE_OVERLAY,
+ Display.TYPE_VIRTUAL);
+ }
+
+ @Test
+ public void displayListReturnsDisplays_excludesVirtualPrivate() {
+ // Add a private virtual display whose uid is different from systemui.
+ final List<Display> displays = createFakeDisplayList(Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL);
+ displays.add(createFakeVirtualPrivateDisplay(/* displayId= */ 2, FAKE_SYSTEMUI_UID + 100));
+ mTestDisplayManagerWrapper.mDisplays = displays;
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(2);
+ });
+
+ List<Display> validDisplays = mA11yms.getValidDisplayList();
+ assertThat(validDisplays).hasSize(2);
+ assertThat(validDisplays)
+ .comparingElementsUsing(
+ Correspondence.transforming(Display::getType, "has a type of"))
+ .doesNotContain(Display.TYPE_VIRTUAL);
+ }
+
+ @Test
+ public void displayListReturnsDisplays_includesVirtualSystemUIPrivate() {
+ // Add a private virtual display whose uid is systemui.
+ final List<Display> displays = createFakeDisplayList(Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL);
+ displays.add(createFakeVirtualPrivateDisplay(/* displayId= */ 2, FAKE_SYSTEMUI_UID));
+ mTestDisplayManagerWrapper.mDisplays = displays;
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(2);
+ });
+
+ List<Display> validDisplays = mA11yms.getValidDisplayList();
+ assertThat(validDisplays).hasSize(3);
+ assertThat(validDisplays)
+ .comparingElementsUsing(
+ Correspondence.transforming(Display::getType, "has a type of"))
+ .contains(Display.TYPE_VIRTUAL);
+ }
+
private Set<String> readStringsFromSetting(String setting) {
final Set<String> result = new ArraySet<>();
mA11yms.readColonDelimitedSettingToSet(
@@ -2422,6 +2501,27 @@ public class AccessibilityManagerServiceTest {
});
}
+ private static List<Display> createFakeDisplayList(int... types) {
+ final ArrayList<Display> displays = new ArrayList<>();
+ for (int i = 0; i < types.length; i++) {
+ final DisplayInfo info = new DisplayInfo();
+ info.type = types[i];
+ final Display display = new Display(DisplayManagerGlobal.getInstance(),
+ i, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ displays.add(display);
+ }
+ return displays;
+ }
+
+ private static Display createFakeVirtualPrivateDisplay(int displayId, int uid) {
+ final DisplayInfo info = new DisplayInfo();
+ info.type = Display.TYPE_VIRTUAL;
+ info.flags |= Display.FLAG_PRIVATE;
+ info.ownerUid = uid;
+ return new Display(DisplayManagerGlobal.getInstance(),
+ displayId, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ }
+
public static class FakeInputFilter extends AccessibilityInputFilter {
FakeInputFilter(Context context,
AccessibilityManagerService service) {
@@ -2506,4 +2606,35 @@ public class AccessibilityManagerServiceTest {
Set<String> setting = readStringsFromSetting(ShortcutUtils.convertToKey(shortcutType));
assertThat(setting).containsExactlyElementsIn(value);
}
+
+ private static class TestDisplayManagerWrapper extends
+ AccessibilityDisplayListener.DisplayManagerWrapper {
+ List<Display> mDisplays;
+ DisplayManager.DisplayListener mRegisteredListener;
+
+ TestDisplayManagerWrapper(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Display[] getDisplays() {
+ return mDisplays.toArray(new Display[0]);
+ }
+
+ @Override
+ public Display getDisplay(int displayId) {
+ for (final Display display : mDisplays) {
+ if (display.getDisplayId() == displayId) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
+ @Nullable Handler handler) {
+ mRegisteredListener = listener;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
index 96ae102e53f3..d0dc2cbb0f86 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
@@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.accessibilityservice.FingerprintGestureController;
@@ -86,7 +86,7 @@ public class FingerprintGestureControllerTest {
mMockFingerprintGestureCallback);
mFingerprintGestureController.onGestureDetectionActiveChanged(true);
mFingerprintGestureController.onGestureDetectionActiveChanged(false);
- verifyZeroInteractions(mMockFingerprintGestureCallback);
+ verifyNoMoreInteractions(mMockFingerprintGestureCallback);
}
@Test
@@ -118,7 +118,7 @@ public class FingerprintGestureControllerTest {
mFingerprintGestureController.onGestureDetectionActiveChanged(true);
mFingerprintGestureController.onGestureDetectionActiveChanged(false);
assertFalse(messageCapturingHandler.hasMessages());
- verifyZeroInteractions(mMockFingerprintGestureCallback);
+ verifyNoMoreInteractions(mMockFingerprintGestureCallback);
messageCapturingHandler.removeAllMessages();
}
@@ -135,7 +135,7 @@ public class FingerprintGestureControllerTest {
mFingerprintGestureController.unregisterFingerprintGestureCallback(
mMockFingerprintGestureCallback);
mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
- verifyZeroInteractions(mMockFingerprintGestureCallback);
+ verifyNoMoreInteractions(mMockFingerprintGestureCallback);
}
@Test
@@ -159,7 +159,7 @@ public class FingerprintGestureControllerTest {
mMockFingerprintGestureCallback);
mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
assertFalse(messageCapturingHandler.hasMessages());
- verifyZeroInteractions(mMockFingerprintGestureCallback);
+ verifyNoMoreInteractions(mMockFingerprintGestureCallback);
messageCapturingHandler.removeAllMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
index c3256cad0fd8..186f7425b189 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
@@ -27,7 +27,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
@@ -159,7 +159,7 @@ public class KeyEventDispatcherTest {
mFilter1SequenceCaptor.getValue());
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
assertFalse(isTimeoutPending(mMessageCapturingHandler));
}
@@ -189,7 +189,7 @@ public class KeyEventDispatcherTest {
mFilter2SequenceCaptor.getValue());
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
assertFalse(isTimeoutPending(mMessageCapturingHandler));
}
@@ -261,7 +261,7 @@ public class KeyEventDispatcherTest {
mKeyEventDispatcher.setOnKeyEventResult(mKeyEventFilter2, false,
mFilter2SequenceCaptor.getValue());
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
assertFalse(isTimeoutPending(mMessageCapturingHandler));
}
@@ -278,7 +278,7 @@ public class KeyEventDispatcherTest {
mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0));
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
}
@Test
@@ -293,7 +293,7 @@ public class KeyEventDispatcherTest {
mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0));
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
}
@Test
@@ -327,7 +327,7 @@ public class KeyEventDispatcherTest {
mFilter1SequenceCaptor.getValue());
assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
}
@Test
@@ -344,7 +344,7 @@ public class KeyEventDispatcherTest {
mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0));
assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 9b8e619c964d..367f2d143acc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -37,7 +37,6 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import android.accessibilityservice.GestureDescription.GestureStep;
@@ -223,7 +222,7 @@ public class MotionEventInjectorTest {
verifyNoMoreInteractions(next);
reset(next);
- verifyZeroInteractions(mServiceInterface);
+ verifyNoMoreInteractions(mServiceInterface);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
verify(next).onMotionEvent(argThat(allOf(mIsLineEnd, hasRightDownTime)),
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 2be43c6f21a5..a0482382fb41 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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -813,6 +814,57 @@ public class AutoclickControllerTest {
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void sendClick_clickType_scroll_showsScrollPanelOnlyOnce() {
+ MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
+ mController.setNext(motionEventCaptor);
+
+ injectFakeMouseActionHoverMoveEvent();
+ // Set delay to zero so click is scheduled to run immediately.
+ mController.mClickScheduler.updateDelay(0);
+
+ // Set click type to scroll.
+ mController.clickPanelController.handleAutoclickTypeChange(
+ AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL);
+
+ // Mock the scroll panel to verify interactions.
+ AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class);
+ mController.mAutoclickScrollPanel = mockScrollPanel;
+
+ // First hover move event.
+ MotionEvent hoverMove1 = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove1.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove1, hoverMove1, /* policyFlags= */ 0);
+ mTestableLooper.processAllMessages();
+
+ // Verify scroll panel is shown once.
+ verify(mockScrollPanel, times(1)).show();
+ assertThat(motionEventCaptor.downEvent).isNull();
+
+ // Second significant hover move event to trigger another autoclick.
+ MotionEvent hoverMove2 = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 200,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 100f,
+ /* y= */ 100f,
+ /* metaState= */ 0);
+ hoverMove2.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove2, hoverMove2, /* policyFlags= */ 0);
+ mTestableLooper.processAllMessages();
+
+ // Verify scroll panel is still only shown once (not called again).
+ verify(mockScrollPanel, times(1)).show();
+ assertThat(motionEventCaptor.downEvent).isNull();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() {
MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
mController.setNext(motionEventCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index 3475c8f5444d..20a95e90b668 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -34,7 +34,6 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
import android.attention.AttentionManagerInternal.ProximityUpdateCallbackInternal;
@@ -196,7 +195,7 @@ public class AttentionManagerServiceTest {
@Test
public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() {
mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal);
- verifyZeroInteractions(mMockProximityUpdateCallbackInternal);
+ verifyNoMoreInteractions(mMockProximityUpdateCallbackInternal);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index 7a770338a34b..f4e87177e072 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -172,7 +172,7 @@ public class FaceDetectClientTest {
client.onInteractionDetected();
client.stopHalOperation();
- verifyZeroInteractions(mVibrator);
+ verifyNoMoreInteractions(mVibrator);
}
private FaceDetectClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index b445226be60f..4fa75b9823e0 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -23,7 +23,7 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -132,8 +132,8 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionAllowlistManager);
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionAllowlistManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -147,7 +147,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager, never()).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -157,8 +157,8 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionAllowlistManager);
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionAllowlistManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -172,7 +172,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager, never()).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -187,7 +187,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager, never()).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -203,7 +203,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -216,8 +216,8 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionAllowlistManager);
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionAllowlistManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -230,8 +230,8 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionAllowlistManager);
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionAllowlistManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -248,7 +248,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -265,7 +265,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -513,7 +513,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
}
@Test
@@ -528,7 +528,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
index 195ab68427b9..9d37b99c5bf4 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.os.Handler;
@@ -98,10 +98,10 @@ public class ContentProtectionAllowlistManagerTest {
@Test
public void constructor() {
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -110,10 +110,10 @@ public class ContentProtectionAllowlistManagerTest {
mTestLooper.dispatchAll();
assertThat(mHandler.hasMessagesOrCallbacks()).isTrue();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -126,8 +126,8 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor, never()).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -142,8 +142,8 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor, never()).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -153,11 +153,11 @@ public class ContentProtectionAllowlistManagerTest {
mContentProtectionAllowlistManager.stop();
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
verify(mMockPackageMonitor, never()).register(any(), any(), any());
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -169,11 +169,11 @@ public class ContentProtectionAllowlistManagerTest {
mContentProtectionAllowlistManager.stop();
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
verify(mMockPackageMonitor, never()).register(any(), any(), any());
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -188,8 +188,8 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -205,8 +205,8 @@ public class ContentProtectionAllowlistManagerTest {
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -223,8 +223,8 @@ public class ContentProtectionAllowlistManagerTest {
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
verify(mMockPackageMonitor, times(2)).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -232,10 +232,10 @@ public class ContentProtectionAllowlistManagerTest {
boolean actual = mContentProtectionAllowlistManager.isAllowed(FIRST_PACKAGE_NAME);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -248,9 +248,9 @@ public class ContentProtectionAllowlistManagerTest {
boolean actual = manager.isAllowed(SECOND_PACKAGE_NAME);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
}
@Test
@@ -263,9 +263,9 @@ public class ContentProtectionAllowlistManagerTest {
boolean actual = manager.isAllowed(FIRST_PACKAGE_NAME);
assertThat(actual).isTrue();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
}
@Test
@@ -276,8 +276,8 @@ public class ContentProtectionAllowlistManagerTest {
manager.mPackageMonitor.onSomePackagesChanged();
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -291,7 +291,7 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockRemoteContentProtectionService)
.onUpdateAllowlistRequest(mMockAllowlistCallback);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -309,7 +309,7 @@ public class ContentProtectionAllowlistManagerTest {
// Does not rethrow
verify(mMockRemoteContentProtectionService)
.onUpdateAllowlistRequest(mMockAllowlistCallback);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -321,8 +321,8 @@ public class ContentProtectionAllowlistManagerTest {
manager.mPackageMonitor.onSomePackagesChanged();
verify(mMockContentCaptureManagerService, times(2)).createRemoteContentProtectionService();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -338,7 +338,7 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
verify(mMockRemoteContentProtectionService)
.onUpdateAllowlistRequest(mMockAllowlistCallback);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -355,7 +355,7 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService, times(2)).createRemoteContentProtectionService();
verify(mMockRemoteContentProtectionService, times(2))
.onUpdateAllowlistRequest(mMockAllowlistCallback);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
index b012aaaed3bf..cd36a1889d2f 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
@@ -27,7 +27,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyCache;
@@ -112,8 +112,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -125,8 +125,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -138,8 +138,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -152,7 +152,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(actual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -166,7 +166,7 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -179,7 +179,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(actual).isFalse();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -192,7 +192,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(actual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -289,8 +289,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -302,8 +302,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -316,7 +316,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(actual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -339,7 +339,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(thirdActual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -362,7 +362,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(thirdActual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -385,7 +385,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(thirdActual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -408,7 +408,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(thirdActual).isTrue();
verify(mMockDevicePolicyManagerInternal, times(3)).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
private void putGlobalSettings(String key, int value) {
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
index 6a7e2865fb32..563a6799e9e5 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
@@ -20,7 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -87,7 +87,7 @@ public class RemoteContentProtectionServiceTest {
@Test
public void doesNotAutoConnect() {
assertThat(mConnectCallCount).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionService);
+ verifyNoMoreInteractions(mMockContentProtectionService);
}
@Test
@@ -124,7 +124,7 @@ public class RemoteContentProtectionServiceTest {
mRemoteContentProtectionService.onServiceConnectionStatusChanged(
mMockContentProtectionService, /* isConnected= */ true);
- verifyZeroInteractions(mMockContentProtectionService);
+ verifyNoMoreInteractions(mMockContentProtectionService);
assertThat(mConnectCallCount).isEqualTo(0);
}
@@ -133,7 +133,7 @@ public class RemoteContentProtectionServiceTest {
mRemoteContentProtectionService.onServiceConnectionStatusChanged(
mMockContentProtectionService, /* isConnected= */ false);
- verifyZeroInteractions(mMockContentProtectionService);
+ verifyNoMoreInteractions(mMockContentProtectionService);
assertThat(mConnectCallCount).isEqualTo(0);
}
diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
index 0f3f27aa2896..9e98af32d6e5 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
@@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -207,7 +207,7 @@ public class ProviderRegistryGetSessionTest {
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
"unsupportedKey", providerPendingIntentResponse);
- verifyZeroInteractions(mGetRequestSession);
+ verifyNoMoreInteractions(mGetRequestSession);
}
@Test
@@ -216,7 +216,7 @@ public class ProviderRegistryGetSessionTest {
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, null);
- verifyZeroInteractions(mGetRequestSession);
+ verifyNoMoreInteractions(mGetRequestSession);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 01bcc2584fe1..c50c62323212 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -88,7 +88,6 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import static org.testng.Assert.assertThrows;
@@ -5305,7 +5304,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// both the user restriction and the policy were set by the PO.
verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(
MANAGED_PROFILE_USER_ID);
- verifyZeroInteractions(getServices().recoverySystem);
+ verifyNoMoreInteractions(getServices().recoverySystem);
}
@Test
@@ -5339,7 +5338,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// not wiped.
verify(getServices().userManagerInternal, never())
.removeUserEvenWhenDisallowed(anyInt());
- verifyZeroInteractions(getServices().recoverySystem);
+ verifyNoMoreInteractions(getServices().recoverySystem);
}
@Test
@@ -5380,7 +5379,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
// DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped.
- verifyZeroInteractions(getServices().recoverySystem);
+ verifyNoMoreInteractions(getServices().recoverySystem);
verify(getServices().userManagerInternal, never())
.removeUserEvenWhenDisallowed(anyInt());
}
@@ -7535,7 +7534,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(getServices().notificationManager, never())
.notify(anyInt(), any(Notification.class));
// Apps shouldn't be suspended.
- verifyZeroInteractions(getServices().ipackageManager);
+ verifyNoMoreInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
setUserUnlocked(CALLER_USER_HANDLE, false);
@@ -7548,7 +7547,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(getServices().notificationManager, never())
.notify(anyInt(), any(Notification.class));
// Apps shouldn't be suspended.
- verifyZeroInteractions(getServices().ipackageManager);
+ verifyNoMoreInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
// Pretend the alarm went off.
@@ -7561,7 +7560,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(getServices().notificationManager, times(1))
.notifyAsUser(any(), anyInt(), any(), any());
// Apps shouldn't be suspended yet.
- verifyZeroInteractions(getServices().ipackageManager);
+ verifyNoMoreInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
clearInvocations(getServices().notificationManager);
@@ -7570,7 +7569,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
// Verify the alarm was not set.
- verifyZeroInteractions(getServices().alarmManager);
+ verifyNoMoreInteractions(getServices().alarmManager);
// Now the user should see a notification about suspended apps.
verify(getServices().notificationManager, times(1))
.notifyAsUser(any(), anyInt(), any(), any());
@@ -8754,7 +8753,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
sendBroadcastWithUser(dpms, Intent.ACTION_MANAGED_PROFILE_REMOVED, CALLER_USER_HANDLE);
// Verify that EuiccManager was not called to delete the subscription.
- verifyZeroInteractions(getServices().euiccManager);
+ verifyNoMoreInteractions(getServices().euiccManager);
}
private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index e20f1e7065d4..a39f07105eab 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -31,7 +31,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.ActivityManagerInternal;
import android.content.Context;
@@ -226,7 +226,7 @@ public class SystemAppUpdateTrackerTest {
assertTrue(!mSystemAppUpdateTracker.getUpdatedApps().contains(DEFAULT_PACKAGE_NAME_2));
// getApplicationLocales should be never be invoked if not a system app.
- verifyZeroInteractions(mMockActivityTaskManager);
+ verifyNoMoreInteractions(mMockActivityTaskManager);
// Broadcast should be never sent if not a system app.
verify(mMockContext, never()).sendBroadcastAsUser(any(), any());
// It shouldn't write to the file if not a system app.
@@ -244,7 +244,7 @@ public class SystemAppUpdateTrackerTest {
Binder.getCallingUid());
// getApplicationLocales should be never be invoked if not installer is not present.
- verifyZeroInteractions(mMockActivityTaskManager);
+ verifyNoMoreInteractions(mMockActivityTaskManager);
// Broadcast should be never sent if installer is not present.
verify(mMockContext, never()).sendBroadcastAsUser(any(), any());
// It shouldn't write to file if no installer present.
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 565a9b6c1c44..4d2dcf65bfeb 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -80,6 +80,10 @@ public class ContextHubEndpointTest {
new HubMessage.Builder(SAMPLE_MESSAGE_TYPE, new byte[] {1, 2, 3, 4, 5})
.setResponseRequired(true)
.build();
+ private static final HubMessage SAMPLE_UNRELIABLE_MESSAGE =
+ new HubMessage.Builder(SAMPLE_MESSAGE_TYPE, new byte[] {1, 2, 3, 4, 5})
+ .setResponseRequired(false)
+ .build();
private ContextHubClientManager mClientManager;
private ContextHubEndpointManager mEndpointManager;
@@ -260,6 +264,24 @@ public class ContextHubEndpointTest {
unregisterExampleEndpoint(endpoint);
}
+ @Test
+ public void testUnreliableMessage() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ int sessionId = openTestSession(endpoint);
+
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_UNRELIABLE_MESSAGE);
+ ArgumentCaptor<HubMessage> messageCaptor = ArgumentCaptor.forClass(HubMessage.class);
+ verify(mMockCallback).onMessageReceived(eq(sessionId), messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE);
+
+ // Confirm we can send another message
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_UNRELIABLE_MESSAGE);
+ verify(mMockCallback, times(2)).onMessageReceived(eq(sessionId), messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
/** A helper method to create a session and validates reliable message sending. */
private void testMessageTransactionInternal(
IContextHubEndpoint endpoint, boolean deliverMessageStatus) throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index a58a9cd2a28f..4a05ea68daec 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -48,7 +48,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -488,7 +488,7 @@ public class MediaProjectionManagerServiceTest {
projection.stop(StopReason.STOP_UNKNOWN);
- verifyZeroInteractions(mMediaProjectionMetricsLogger);
+ verifyNoMoreInteractions(mMediaProjectionMetricsLogger);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java
index d55f96782084..2ed27048c288 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java
@@ -23,7 +23,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
@@ -273,7 +273,7 @@ public class DynamicCodeLoggerTests {
assertThat(mMessagesForUid).isEmpty();
assertThat(mWriteTriggered).isFalse();
- verifyZeroInteractions(mPM);
+ verifyNoMoreInteractions(mPM);
}
@Test
diff --git a/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java b/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
index c8afb78bc12f..630a7e47fa48 100644
--- a/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
+++ b/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
@@ -23,7 +23,7 @@ import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
@@ -126,7 +126,7 @@ public final class GnssTimeUpdateServiceTest {
locationListener.onLocationChanged(location);
verify(mMockLocationManager).removeUpdates(locationListener);
- verifyZeroInteractions(mMockTimeDetectorInternal);
+ verifyNoMoreInteractions(mMockTimeDetectorInternal);
verify(mMockAlarmManager).set(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
anyLong(),
@@ -150,7 +150,7 @@ public final class GnssTimeUpdateServiceTest {
// Verify the service returned to location listening.
verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any());
- verifyZeroInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
+ verifyNoMoreInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
}
// Tests what happens when a call is made to startGnssListeningInternal() when service is
@@ -172,7 +172,7 @@ public final class GnssTimeUpdateServiceTest {
// listening again.
verify(mMockAlarmManager).cancel(alarmListenerCaptor.getValue());
verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any());
- verifyZeroInteractions(mMockTimeDetectorInternal);
+ verifyNoMoreInteractions(mMockTimeDetectorInternal);
}
private void advanceServiceToSleepingState(
@@ -190,7 +190,7 @@ public final class GnssTimeUpdateServiceTest {
any(), any(), any(), locationListenerCaptor.capture());
LocationListener locationListener = locationListenerCaptor.getValue();
Location location = new Location(LocationManager.GPS_PROVIDER);
- verifyZeroInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
+ verifyNoMoreInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
locationListener.onLocationChanged(location);
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 9930c9f07ed8..7b1ce446da7c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -62,7 +62,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -992,7 +991,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
() -> mService.requestProjection(mBinder, PROJECTION_TYPE_NONE, PACKAGE_NAME));
verify(mContext, never()).enforceCallingPermission(
eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
- verifyZeroInteractions(mBinder);
+ verifyNoMoreInteractions(mBinder);
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
}
@@ -1008,7 +1007,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
() -> mService.requestProjection(mBinder, multipleProjectionTypes, PACKAGE_NAME));
verify(mContext, never()).enforceCallingPermission(
eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
- verifyZeroInteractions(mBinder);
+ verifyNoMoreInteractions(mBinder);
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 5ce9a3e8d4d4..8023bdd08927 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -36,7 +36,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
@@ -184,7 +183,7 @@ public class DefaultDeviceEffectsApplierTest {
mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
- verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
+ verifyNoMoreInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
}
@Test
@@ -252,8 +251,8 @@ public class DefaultDeviceEffectsApplierTest {
// Wallpaper dimming was undone, Grayscale was applied, nothing else was touched.
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
verify(mColorDisplayManager).setSaturationLevel(eq(0));
- verifyZeroInteractions(mPowerManager);
- verifyZeroInteractions(mUiModeManager);
+ verifyNoMoreInteractions(mPowerManager);
+ verifyNoMoreInteractions(mUiModeManager);
}
@Test
@@ -269,7 +268,7 @@ public class DefaultDeviceEffectsApplierTest {
ORIGIN_APP);
// Effect was not yet applied, but a broadcast receiver was registered.
- verifyZeroInteractions(mUiModeManager);
+ verifyNoMoreInteractions(mUiModeManager);
verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(),
intentFilterCaptor.capture(), anyInt());
assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF);
@@ -337,7 +336,7 @@ public class DefaultDeviceEffectsApplierTest {
origin.value());
// Effect was not applied, will be on next screen-off.
- verifyZeroInteractions(mUiModeManager);
+ verifyNoMoreInteractions(mUiModeManager);
verify(mContext).registerReceiver(any(),
argThat(filter -> Intent.ACTION_SCREEN_OFF.equals(filter.getAction(0))),
anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 46be9a57ce57..11143653a75d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -60,7 +60,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
@@ -313,7 +313,7 @@ public class GroupHelperTest extends UiServiceTestCase {
getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
false);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -327,7 +327,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
mGroupHelper.onNotificationPosted(
getNotificationRecord(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -340,7 +340,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
mGroupHelper.onNotificationPosted(
getNotificationRecord(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -353,7 +353,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(
getNotificationRecord(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a", false),
false);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1744,7 +1744,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1759,7 +1759,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1775,7 +1775,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1791,7 +1791,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1811,7 +1811,7 @@ public class GroupHelperTest extends UiServiceTestCase {
String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, "testGrp", true);
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1830,7 +1830,7 @@ public class GroupHelperTest extends UiServiceTestCase {
String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.of(7), "testGrp", true);
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1853,7 +1853,7 @@ public class GroupHelperTest extends UiServiceTestCase {
String.valueOf(AUTOGROUP_AT_COUNT + 1), UserHandle.SYSTEM, "testGrp", false);
notificationList.add(child);
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1877,7 +1877,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(child);
summaryByGroup.put(summary.getGroupKey(), summary);
mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -2209,7 +2209,7 @@ public class GroupHelperTest extends UiServiceTestCase {
childrenToRemove.add(child);
}
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
// Remove all child notifications from the valid group => summary without children
Mockito.reset(mCallback);
@@ -2273,7 +2273,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
}
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
// Remove some child notifications from the valid group, transform into a singleton group
Mockito.reset(mCallback);
@@ -2329,7 +2329,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(child);
}
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
// Remove all child notifications from the valid group => summary without children
Mockito.reset(mCallback);
@@ -2343,7 +2343,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
summaryByGroup);
// Check that nothing was force grouped
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -3837,7 +3837,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@@ -3861,7 +3861,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
}
// FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS is disabled => don't force group
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -4498,7 +4498,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPostedWithDelay(extra, notifList, summaryByGroupKey);
// no autogrouping should have occurred
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
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 a02f628ce9b7..43228f434997 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -96,7 +96,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
@@ -6372,7 +6372,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel same = new NotificationChannel("id", "Bah", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_P, 0, same, true, false, 0, false);
- verifyZeroInteractions(mHandler);
+ verifyNoMoreInteractions(mHandler);
}
@Test
@@ -6398,7 +6398,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.updateNotificationChannel(PKG_P, 0, same, false, 0, false);
- verifyZeroInteractions(mHandler);
+ verifyNoMoreInteractions(mHandler);
}
@Test
@@ -6412,7 +6412,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void setShowBadge_same_doesNotRequestSort() {
mHelper.setShowBadge(PKG_P, 0, true); // true == DEFAULT_SHOW_BADGE
- verifyZeroInteractions(mHandler);
+ verifyNoMoreInteractions(mHandler);
}
@Test
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 79e272b7ec01..01698b5bdd6b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -33,7 +33,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
@@ -133,7 +132,7 @@ public class VibratorControlServiceTest {
mVibratorControlService.registerVibratorController(controller1);
mVibratorControlService.unregisterVibratorController(controller2);
- verifyZeroInteractions(mMockVibrationScaler);
+ verifyNoMoreInteractions(mMockVibrationScaler);
assertThat(controller1.isLinkedToDeath).isTrue();
}
@@ -187,7 +186,7 @@ public class VibratorControlServiceTest {
verify(mStatsLoggerMock).logVibrationParamResponseIgnored();
verifyNoMoreInteractions(mStatsLoggerMock);
- verifyZeroInteractions(mMockVibrationScaler);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test(expected = IllegalArgumentException.class)
@@ -242,7 +241,7 @@ public class VibratorControlServiceTest {
mFakeVibratorController);
verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
- verifyZeroInteractions(mMockVibrationScaler);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test(expected = IllegalArgumentException.class)
@@ -280,7 +279,7 @@ public class VibratorControlServiceTest {
mFakeVibratorController);
verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
- verifyZeroInteractions(mMockVibrationScaler);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
index 9a59ede20e23..011971d85f42 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger_middleware.RecognitionEventSys;
@@ -76,7 +75,7 @@ public class SoundTriggerHalConcurrentCaptureHandlerTest {
assertEquals(event.halEventReceivedMillis, -1);
assertEquals(event.recognitionEvent.status, RecognitionStatus.ABORTED);
assertFalse(event.recognitionEvent.recognitionStillActive);
- verifyZeroInteractions(mGlobalCallback);
+ verifyNoMoreInteractions(mGlobalCallback);
clearInvocations(callback, mUnderlying);
mNotifier.setActive(false);
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 8992c2e632b2..7f242dea9f45 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2021,8 +2021,6 @@ public class ActivityRecordTests extends WindowTestsBase {
display.setFixedRotationLaunchingAppUnchecked(activity);
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
- assertTrue(displayRotation.isRotatingSeamlessly());
-
// The launching rotated app should not be cleared when waiting for remote rotation.
display.continueUpdateOrientationForDiffOrienLaunchingApp();
assertTrue(display.isFixedRotationLaunchingApp(activity));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 86d901b640ff..862ce51d3459 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -20,6 +20,7 @@ import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
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 com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -75,13 +76,15 @@ import java.util.concurrent.TimeUnit;
* Tests for the {@link ActivityTaskSupervisor} class.
*
* Build/Install/Run:
- * atest WmTests:ActivityTaskSupervisorTests
+ * atest WmTests:ActivityTaskSupervisorTests
*/
@MediumTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityTaskSupervisorTests extends WindowTestsBase {
private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+ private static final int DEFAULT_CALLING_PID = -1;
+ private static final int DEFAULT_CALLING_UID = -1;
/**
* Ensures that an activity is removed from the stopping activities list once it is resumed.
@@ -110,7 +113,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
.setCreateTask(true).build();
final ConditionVariable condition = new ConditionVariable();
final WaitResult taskToFrontWait = new WaitResult();
- final ComponentName[] launchedComponent = { null };
+ final ComponentName[] launchedComponent = {null};
// Create a new thread so the waiting method in test can be notified.
new Thread(() -> {
synchronized (mAtm.mGlobalLock) {
@@ -408,7 +411,8 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
anyInt(), any());
- mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+ mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID,
+ activity.getRootTaskId(), safeOptions);
assertThat(activity.mLaunchCookie).isEqualTo(launchCookie);
verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
@@ -426,12 +430,62 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
anyInt(), any());
- mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+ mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID,
+ activity.getRootTaskId(), safeOptions);
assertThat(activity.mLaunchCookie).isNull();
verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
}
+ /** Verifies that launch from recents doesn't set the launch cookie on the activity. */
+ @Test
+ public void testStartActivityFromRecents_inMultiWindowRootTask_homeNotMoved() {
+ final Task multiWindowRootTask = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW).setOnTop(true).build();
+
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setParentTask(
+ multiWindowRootTask).setCreateTask(true).build();
+
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+ ActivityOptions.makeBasic().toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid());
+
+ doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+ anyInt(), any());
+
+ mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID,
+ activity.getRootTaskId(), safeOptions);
+
+ verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+ verify(mRootWindowContainer.getDefaultTaskDisplayArea(), never()).moveHomeRootTaskToFront(
+ any());
+ verify(multiWindowRootTask.getDisplayArea(), never()).moveHomeRootTaskToFront(any());
+ }
+
+ /** Verifies that launch from recents doesn't set the launch cookie on the activity. */
+ @Test
+ public void testStartActivityFromRecents_inFullScreenRootTask_homeMovedToFront() {
+ final Task fullscreenRootTask = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FULLSCREEN).setOnTop(true).build();
+
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setParentTask(
+ fullscreenRootTask).setCreateTask(true).build();
+
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+ ActivityOptions.makeBasic().toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid());
+
+ doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+ anyInt(), any());
+
+ mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID,
+ activity.getRootTaskId(), safeOptions);
+
+ verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+ verify(mRootWindowContainer.getDefaultTaskDisplayArea()).moveHomeRootTaskToFront(any());
+ verify(fullscreenRootTask.getDisplayArea()).moveHomeRootTaskToFront(any());
+ }
+
@Test
public void testOpaque_leafTask_occludingActivity_isOpaque() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java
new file mode 100644
index 000000000000..bdd57bce7c09
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatSafeRegionPolicy}.
+ * Build/Install/Run:
+ * atest WmTests:AppCompatSafeRegionPolicyTests
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+@EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+public class AppCompatSafeRegionPolicyTests extends WindowTestsBase {
+
+ @Test
+ public void testHasNeedsSafeRegion_returnTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setNeedsSafeRegionBounds(/*needsSafeRegionBounds*/ true);
+
+ robot.checkNeedsSafeRegionBounds(/* expected */ true);
+ });
+ }
+
+ @Test
+ public void testDoesNotHaveNeedsSafeRegion_returnFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setNeedsSafeRegionBounds(/*needsSafeRegionBounds*/ false);
+
+ robot.checkNeedsSafeRegionBounds(/* expected */ false);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<AppCompatSafeRegionPolicyRobotTest> consumer) {
+ final AppCompatSafeRegionPolicyRobotTest robot =
+ new AppCompatSafeRegionPolicyRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class AppCompatSafeRegionPolicyRobotTest extends AppCompatRobotBase {
+
+ AppCompatSafeRegionPolicyRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getSafeRegionPolicy());
+ }
+
+ AppCompatSafeRegionPolicy getAppCompatSafeRegionPolicy() {
+ return activity().top().mAppCompatController.getSafeRegionPolicy();
+ }
+
+ void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) {
+ doReturn(needsSafeRegionBounds).when(
+ getAppCompatSafeRegionPolicy()).getNeedsSafeRegionBounds();
+ }
+
+ void checkNeedsSafeRegionBounds(boolean expected) {
+ assertEquals(expected, getAppCompatSafeRegionPolicy().getNeedsSafeRegionBounds());
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index bfd533aa8f79..b2cfbbd23b22 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -98,6 +98,24 @@ public class AppCompatUtilsTest extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void getLetterboxReasonString_isLetterboxedForSafeRegionOnly() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+ /* forFixedOrientationAndAspectRatio */ false);
+ robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
+ robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ false);
+ robot.setIsLetterboxedForSafeRegionOnlyAllowed(/* safeRegionOnly */ true);
+
+ robot.checkTopActivityLetterboxReason(/* expected */ "SAFE_REGION");
+ });
+ }
+
+ @Test
public void getLetterboxReasonString_aspectRatio() {
runTestScenario((robot) -> {
robot.applyOnActivity((a) -> {
@@ -124,6 +142,7 @@ public class AppCompatUtilsTest extends WindowTestsBase {
/* forFixedOrientationAndAspectRatio */ false);
robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ false);
+ robot.setIsLetterboxedForSafeRegionOnlyAllowed(/* safeRegionOnly */ false);
robot.checkTopActivityLetterboxReason(/* expected */ "UNKNOWN_REASON");
});
@@ -253,6 +272,7 @@ public class AppCompatUtilsTest extends WindowTestsBase {
void onPostActivityCreation(@NonNull ActivityRecord activity) {
super.onPostActivityCreation(activity);
spyOn(activity.mAppCompatController.getAspectRatioPolicy());
+ spyOn(activity.mAppCompatController.getSafeRegionPolicy());
}
@Override
@@ -286,6 +306,11 @@ public class AppCompatUtilsTest extends WindowTestsBase {
when(mWindowState.isLetterboxedForDisplayCutout()).thenReturn(displayCutout);
}
+ void setIsLetterboxedForSafeRegionOnlyAllowed(boolean safeRegionOnly) {
+ when(activity().top().mAppCompatController.getSafeRegionPolicy()
+ .isLetterboxedForSafeRegionOnlyAllowed()).thenReturn(safeRegionOnly);
+ }
+
void setFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) {
doReturn(mode).when(activity().top().mDisplayContent.mAppCompatCameraPolicy
.mCameraCompatFreeformPolicy).getCameraCompatMode(activity().top());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 3e86f7b57294..00b617e91bfd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -383,7 +383,7 @@ public class DesktopModeLaunchParamsModifierTests extends
spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
mActivity.mAppCompatController.getAspectRatioOverrides())
- .isUserFullscreenOverrideEnabled();
+ .hasFullscreenOverride();
final int desiredWidth =
(int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
@@ -411,7 +411,7 @@ public class DesktopModeLaunchParamsModifierTests extends
spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
mActivity.mAppCompatController.getAspectRatioOverrides())
- .isSystemOverrideToFullscreenEnabled();
+ .hasFullscreenOverride();
final int desiredWidth =
(int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
@@ -1170,6 +1170,32 @@ public class DesktopModeLaunchParamsModifierTests extends
@Test
@EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSizeWithFullscreenOverride_noModifications() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchBounds(new Rect(
+ DISPLAY_STABLE_BOUNDS.left,
+ DISPLAY_STABLE_BOUNDS.top,
+ /* right = */ 500,
+ /* bottom = */ 500))
+ .setFlexibleLaunchSize(true);
+ spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
+ doReturn(true).when(
+ mActivity.mAppCompatController.getAspectRatioOverrides())
+ .hasFullscreenOverride();
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(options.getLaunchBounds(), mResult.mBounds);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
public void testOptionsBoundsSet_flexibleLaunchSize_boundsSizeModified() {
setupDesktopModeLaunchParamsModifier();
@@ -1493,6 +1519,24 @@ public class DesktopModeLaunchParamsModifierTests extends
assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
}
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
+ public void testFreeformWindowingModeAppliedIfSourceTaskExists() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).build();
+ final Task sourceTask = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ final ActivityRecord sourceActivity = new ActivityBuilder(task.mAtmService)
+ .setTask(sourceTask).build();
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+ .setSource(sourceActivity).calculate());
+ assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
+ }
+
private Task createTask(DisplayContent display, Boolean isResizeable) {
final int resizeMode = isResizeable ? RESIZE_MODE_RESIZEABLE
: RESIZE_MODE_UNRESIZEABLE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index a30591ea7b15..9486bc522a9c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -25,6 +25,7 @@ import static com.android.server.wm.utils.LastCallVerifier.lastCall;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -432,4 +433,32 @@ public class DimmerTests extends WindowTestsBase {
verify(mTransaction, never()).setAlpha(dimLayer, 0.5f);
verify(mTransaction).setAlpha(dimLayer, 0.9f);
}
+
+ /**
+ * A window requesting to dim to 0 and without blur would cause the dim to be created and
+ * destroyed continuously.
+ * Ensure the dim layer is not created until the window is requesting valid values.
+ */
+ @Test
+ public void testDimNotCreatedIfNoAlphaNoBlur() {
+ mDimmer.adjustAppearance(mChild1, 0.0f, 0);
+ mDimmer.adjustPosition(mChild1, mChild1);
+ assertNull(mDimmer.getDimLayer());
+ mDimmer.updateDims(mTransaction);
+ assertNull(mDimmer.getDimLayer());
+
+ mDimmer.adjustAppearance(mChild1, 0.9f, 0);
+ mDimmer.adjustPosition(mChild1, mChild1);
+ assertNotNull(mDimmer.getDimLayer());
+ }
+
+ /**
+ * If there is a blur, then the dim layer is created even though alpha is 0
+ */
+ @Test
+ public void testDimCreatedIfNoAlphaButHasBlur() {
+ mDimmer.adjustAppearance(mChild1, 0.0f, 10);
+ mDimmer.adjustPosition(mChild1, mChild1);
+ assertNotNull(mDimmer.getDimLayer());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 0af41ea1f634..89aa3b9a2443 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -56,7 +56,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.pm.ActivityInfo;
@@ -174,7 +174,7 @@ public class DisplayAreaTest extends WindowTestsBase {
da1.reduceOnAllTaskDisplayAreas(callback2, 0);
da1.getItemFromTaskDisplayAreas(callback3);
- verifyZeroInteractions(da2);
+ verifyNoMoreInteractions(da2);
// Traverse the child if the current DA has type ANY
final DisplayArea<WindowContainer> da3 = new DisplayArea<>(mWm, ANY, "DA3");
@@ -207,7 +207,7 @@ public class DisplayAreaTest extends WindowTestsBase {
da5.reduceOnAllTaskDisplayAreas(callback2, 0);
da5.getItemFromTaskDisplayAreas(callback3);
- verifyZeroInteractions(da6);
+ verifyNoMoreInteractions(da6);
}
@Test
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 4c81f738138a..25fdedf32908 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1706,8 +1706,6 @@ public class DisplayContentTests extends WindowTestsBase {
app.setVisible(true);
doReturn(false).when(app).inTransition();
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
- mStatusBarWindow.finishSeamlessRotation(t);
- mNavBarWindow.finishSeamlessRotation(t);
// The fixed rotation should be cleared and the new rotation is applied to display.
assertFalse(app.hasFixedRotationTransform());
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index 0d5828a3c4e1..ae005f228969 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -117,6 +117,7 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
expected.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class);
expected.mWindowingMode = WINDOWING_MODE_PINNED;
expected.mBounds.set(200, 300, 400, 500);
+ expected.mNeedsSafeRegionBounds = true;
mPersister.putLaunchParams(userId, name, expected);
@@ -189,6 +190,7 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
params.mWindowingMode = WINDOWING_MODE_FREEFORM;
params.mBounds.set(0, 0, 30, 20);
params.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class);
+ params.mNeedsSafeRegionBounds = true;
final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE,
params);
@@ -229,6 +231,158 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
}
/**
+ * Tests only needs safe region bounds are not propagated if results are skipped.
+ */
+ @Test
+ public void testSkip_needsSafeRegionBoundsNotModified() {
+ final LaunchParams params1 = new LaunchParams();
+ params1.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_SKIP, params1);
+
+ final LaunchParams params2 = new LaunchParams();
+ params2.mNeedsSafeRegionBounds = false;
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+ mController.registerModifier(positioner1);
+ mController.registerModifier(positioner2);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ assertEquals(result, positioner2.getLaunchParams());
+ }
+
+ /**
+ * Tests only needs safe region bounds are propagated even if results are continued.
+ */
+ @Test
+ public void testContinue_needsSafeRegionBoundsCarriedOver() {
+ final LaunchParams params1 = new LaunchParams();
+ final InstrumentedPositioner positioner1 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params1);
+
+ final LaunchParams params2 = new LaunchParams();
+ params2.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+ mController.registerModifier(positioner1);
+ mController.registerModifier(positioner2);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ // Safe region is propagated from positioner1
+ assertEquals(result.mNeedsSafeRegionBounds,
+ positioner2.getLaunchParams().mNeedsSafeRegionBounds);
+ assertEquals(result.mWindowingMode, positioner1.getLaunchParams().mWindowingMode);
+ assertEquals(result.mBounds, positioner1.getLaunchParams().mBounds);
+ assertEquals(result.mPreferredTaskDisplayArea,
+ positioner1.getLaunchParams().mPreferredTaskDisplayArea);
+ }
+
+ /**
+ * Tests needs safe region bounds are modified if results from the next continue have been set.
+ */
+ @Test
+ public void testContinue_needsSafeRegionBoundsModifiedFromLaterContinue() {
+ final LaunchParams params1 = new LaunchParams();
+ params1.mNeedsSafeRegionBounds = false;
+ final InstrumentedPositioner positioner1 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params1);
+
+ final LaunchParams params2 = new LaunchParams();
+ params2.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+ mController.registerModifier(positioner1);
+ mController.registerModifier(positioner2);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ // Safe region is propagated from positioner1
+ assertEquals(result.mNeedsSafeRegionBounds,
+ positioner1.getLaunchParams().mNeedsSafeRegionBounds);
+ assertEquals(result.mWindowingMode, positioner2.getLaunchParams().mWindowingMode);
+ assertEquals(result.mBounds, positioner2.getLaunchParams().mBounds);
+ assertEquals(result.mPreferredTaskDisplayArea,
+ positioner2.getLaunchParams().mPreferredTaskDisplayArea);
+ }
+
+ /**
+ * Tests only needs safe region bounds are propagated to result done even if there are skipped
+ * and continued results and continue sets true for needs safe region bounds.
+ */
+ @Test
+ public void testDone_ContinueSetsNeedsSafeRegionBounds() {
+ final LaunchParams params1 = new LaunchParams();
+ final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_DONE, params1);
+
+ final LaunchParams params2 = new LaunchParams();
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+ final LaunchParams params3 = new LaunchParams();
+ final InstrumentedPositioner positioner3 = new InstrumentedPositioner(RESULT_SKIP, params3);
+
+ final LaunchParams params4 = new LaunchParams();
+ params4.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner4 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params4);
+
+ final LaunchParams params5 = new LaunchParams();
+ params5.mNeedsSafeRegionBounds = false;
+ final InstrumentedPositioner positioner5 = new InstrumentedPositioner(RESULT_SKIP, params5);
+
+ mController.registerModifier(positioner1);
+ mController.registerModifier(positioner2);
+ mController.registerModifier(positioner3);
+ mController.registerModifier(positioner4);
+ mController.registerModifier(positioner5);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ // Safe region is propagated from positioner4
+ assertEquals(result.mNeedsSafeRegionBounds,
+ positioner4.getLaunchParams().mNeedsSafeRegionBounds);
+ assertEquals(result.mWindowingMode, positioner1.getLaunchParams().mWindowingMode);
+ assertEquals(result.mBounds, positioner1.getLaunchParams().mBounds);
+ assertEquals(result.mPreferredTaskDisplayArea,
+ positioner1.getLaunchParams().mPreferredTaskDisplayArea);
+ }
+
+ /**
+ * Tests only needs safe region bounds are set if results are done.
+ */
+ @Test
+ public void testDone_needsSafeRegionBoundsModified() {
+ final LaunchParams params = new LaunchParams();
+ params.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
+
+ mController.registerModifier(positioner);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ assertEquals(result, positioner.getLaunchParams());
+ }
+
+ /**
* Tests preferred display id calculation for VR.
*/
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 8a7e7434e604..2c6884e7a35a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -52,7 +52,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static java.lang.Integer.MAX_VALUE;
@@ -1293,7 +1293,7 @@ public class RecentTasksTest extends WindowTestsBase {
// Add secondTask to top again
mRecentTasks.add(secondTask);
- verifyZeroInteractions(controller);
+ verifyNoMoreInteractions(controller);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e2c4a1d2dfea..fc70b80790c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4738,6 +4738,213 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_noManifestProperty_returnsTrue() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ // For an activity letterboxed only due to safe region, areBoundsLetterboxed will return
+ // false
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+ // Since no manifest property is defined, the activity is opted in by default
+ assertTrue(mActivity.mAppCompatController.getSafeRegionPolicy()
+ .isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_allowedForActivity_returnsTrue() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ // Activity can opt-out the safe region letterboxing by component level property.
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */);
+ updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);
+
+ // Since activity manifest property is defined as true, the activity can be letterboxed
+ // for safe region
+ assertTrue(optOutActivity.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_notAllowedForActivity_returnsFalse() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);
+
+ // Since activity manifest property is defined as false, the activity can not be letterboxed
+ // for safe region
+ assertFalse(optOutActivity.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_notAllowedForApplication_returnsFalse() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ optOutAppActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);
+
+ // Since application manifest property is defined as false, the activity can not be
+ // letterboxed for safe region
+ assertFalse(optOutAppActivity.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_allowedForApplication_returnsTrue() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */);
+ final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ optOutAppActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);
+
+ // Since application manifest property is defined as true, the activity can be letterboxed
+ // for safe region
+ assertTrue(optOutAppActivity.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ private void updateApplicationLevelAllowSafeRegionLetterboxingProperty(ComponentName name,
+ PackageManager pm, boolean propertyValueForApplication) {
+ final PackageManager.Property propertyForApplication = new PackageManager.Property(
+ "propertyName", /* value */ propertyValueForApplication, name.getPackageName(),
+ name.getClassName());
+ try {
+ doReturn(propertyForApplication).when(pm).getPropertyAsUser(
+ WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
+ name.getPackageName(), /* className */ null, /* userId */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void updateActivityLevelAllowSafeRegionLetterboxingProperty(ComponentName name,
+ PackageManager pm, boolean propertyValueForActivity) {
+ final PackageManager.Property propertyForActivity = new PackageManager.Property(
+ "propertyName", /* value */ propertyValueForActivity, name.getPackageName(),
+ name.getClassName());
+ try {
+ doReturn(propertyForActivity).when(pm).getPropertyAsUser(
+ WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
+ name.getPackageName(), name.getClassName(), /* userId */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testAreBoundsLetterboxed_letterboxedForSafeRegionAndFixedOrientation_returnTrue() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ final Rect safeRegionBounds = setupSafeRegionBoundsParameters(/* dw */ 500, /* dh */ 200);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity is letterboxed due to fixed orientation within the safe region
+ assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION);
+ assertFalse(mActivity.mAppCompatController.getSafeRegionPolicy()
+ .isLetterboxedForSafeRegionOnlyAllowed());
+ assertTrue(safeRegionBounds.contains(mActivity.getBounds()));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testAreBoundsLetterboxed_letterboxedForSafeRegionAndAspectRatio_returnTrue() {
+ setUpPortraitLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ final Rect safeRegionBounds = setupSafeRegionBoundsParameters(/* dw */ 200, /* dh */ 300);
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity is letterboxed due to min aspect ratio within the safe region
+ assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
+ .isLetterboxedForAspectRatioOnly());
+ assertFalse(mActivity.inSizeCompatMode());
+ assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);
+ assertTrue(safeRegionBounds.contains(mActivity.getBounds()));
+ }
+
+ private Rect setupSafeRegionBoundsParameters(int dw, int dh) {
+ final AppCompatController appCompatController = mActivity.mAppCompatController;
+ final AppCompatSafeRegionPolicy safeRegionPolicy =
+ appCompatController.getSafeRegionPolicy();
+ safeRegionPolicy.setNeedsSafeRegionBounds(true);
+ spyOn(mTask);
+ final Rect safeRegionBounds = new Rect(100, 200, 100 + dw, 200 + dh);
+ doReturn(safeRegionBounds).when(mTask).getSafeRegionBounds();
+ return safeRegionBounds;
+ }
+
+ @Test
public void testAreBoundsLetterboxed_letterboxedForSizeCompat_returnsTrue() {
setUpDisplaySizeWithApp(1000, 2500);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
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 4f310de1e48b..e0e65e67762e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -1431,6 +1431,93 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBounds_appliedOnNodeAndChildren() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+
+ assertNull(root.getSafeRegionBounds());
+ assertNull(child1.getSafeRegionBounds());
+ assertNull(child11.getSafeRegionBounds());
+ assertNull(child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+
+ final Rect tempSafeRegionBounds1 = new Rect(50, 50, 200, 300);
+ child1.setSafeRegionBounds(tempSafeRegionBounds1);
+
+ assertNull(root.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+
+ // Set different safe region bounds on child11
+ final Rect tempSafeRegionBounds2 = new Rect(30, 30, 200, 200);
+ child11.setSafeRegionBounds(tempSafeRegionBounds2);
+
+ assertNull(root.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds2, child11.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBounds_resetSafeRegionBounds() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+
+ assertNull(root.getSafeRegionBounds());
+ assertNull(child1.getSafeRegionBounds());
+ assertNull(child11.getSafeRegionBounds());
+ assertNull(child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+
+ final Rect tempSafeRegionBounds1 = new Rect(50, 50, 200, 300);
+ child1.setSafeRegionBounds(tempSafeRegionBounds1);
+
+ assertNull(root.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+
+ // Set different safe region bounds on child11
+ final Rect tempSafeRegionBounds2 = new Rect(30, 30, 200, 200);
+ child11.setSafeRegionBounds(tempSafeRegionBounds2);
+
+ assertEquals(tempSafeRegionBounds2, child11.getSafeRegionBounds());
+
+ // Reset safe region bounds on child11. Now child11 will use child1 safe region bounds.
+ child11.setSafeRegionBounds(/* safeRegionBounds */null);
+
+ assertNull(root.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+ }
+
+ @Test
public void testSetExcludeInsetsTypes_appliedOnNodeAndChildren() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index dcb68620e361..c0be214241a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -36,8 +36,10 @@ import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.times;
import android.content.Intent;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -46,6 +48,8 @@ import android.window.WindowContainerTransaction.HierarchyOp;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -62,6 +66,8 @@ import java.util.List;
@Presubmit
@RunWith(WindowTestRunner.class)
public class WindowContainerTransactionTests extends WindowTestsBase {
+ private final Rect mSafeRegionBounds = new Rect(50, 50, 200, 300);
+
@Test
public void testRemoveTask() {
final Task rootTask = createTask(mDisplayContent);
@@ -209,6 +215,152 @@ public class WindowContainerTransactionTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnTaskDisplayArea() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = taskDisplayArea.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the task display area
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(taskDisplayArea.getSafeRegionBounds(), mSafeRegionBounds);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the root task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = task.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(rootTask.getSafeRegionBounds());
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnTask_resetSafeRegionBounds() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = task.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(rootTask.getSafeRegionBounds());
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+
+ // Reset safe region bounds on the task
+ wct.setSafeRegionBounds(token, /* safeRegionBounds */null);
+ applyTransaction(wct);
+
+ assertNull(activity.getSafeRegionBounds());
+ assertNull(task.getSafeRegionBounds());
+ assertNull(rootTask.getSafeRegionBounds());
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTaskAndTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the root task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ // Set different safe region bounds on task
+ final Rect tempSafeRegionBounds = new Rect(30, 30, 200, 200);
+ wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(), tempSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), tempSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), tempSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTaskAndTask_resetSafeRegionBoundsOnTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the root task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ // Set different safe region bounds on task
+ final Rect mTmpSafeRegionBounds = new Rect(30, 30, 200, 200);
+ wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(), mTmpSafeRegionBounds);
+ applyTransaction(wct);
+
+ // Task and activity will use different safe region bounds
+ assertEquals(activity.getSafeRegionBounds(), mTmpSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mTmpSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+
+ // Reset safe region bounds on task
+ wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(),
+ /* safeRegionBounds */null);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
public void testDesktopMode_moveTaskToFront() {
final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 5401a44d7016..6a35fa8e20ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -61,6 +61,7 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -1568,6 +1569,51 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTask() {
+ Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_FULLSCREEN, null);
+ final Task task1 = createRootTask();
+ final Task task2 = createTask(rootTask, false /* fakeDraw */);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ Rect safeRegionBounds = new Rect(50, 50, 200, 300);
+
+ wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(), safeRegionBounds);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertEquals(rootTask.getSafeRegionBounds(), safeRegionBounds);
+ assertEquals(task2.getSafeRegionBounds(), safeRegionBounds);
+ assertNull(task1.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTask_resetSafeRegionBounds() {
+ Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_FULLSCREEN, null);
+ final Task task1 = createRootTask();
+ final Task task2 = createTask(rootTask, false /* fakeDraw */);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ Rect safeRegionBounds = new Rect(50, 50, 200, 300);
+
+ wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(), safeRegionBounds);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertEquals(rootTask.getSafeRegionBounds(), safeRegionBounds);
+ assertEquals(task2.getSafeRegionBounds(), safeRegionBounds);
+ assertNull(task1.getSafeRegionBounds());
+
+ // Reset safe region bounds on the root task
+ wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(),
+ /* safeRegionBounds */null);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertNull(rootTask.getSafeRegionBounds());
+ assertNull(task2.getSafeRegionBounds());
+ assertNull(task1.getSafeRegionBounds());
+ }
+
+ @Test
public void testReparentToOrganizedTask() {
final ITaskOrganizer organizer = registerMockOrganizer();
Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 59ee2f5c8e9f..5624677779a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -23,9 +23,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsSource.ID_IME;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -88,7 +85,6 @@ import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -673,56 +669,6 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
- public void testSeamlesslyRotateWindow() {
- final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
- final SurfaceControl.Transaction t = spy(StubTransaction.class);
-
- makeWindowVisible(app);
- app.mSurfaceControl = mock(SurfaceControl.class);
- final Rect frame = app.getFrame();
- frame.set(10, 20, 60, 80);
- app.updateSurfacePosition(t);
- assertTrue(app.mLastSurfacePosition.equals(frame.left, frame.top));
- app.seamlesslyRotateIfAllowed(t, ROTATION_0, ROTATION_90, true /* requested */);
- assertTrue(app.mSeamlesslyRotated);
-
- // Verify we un-rotate the window state surface.
- final Matrix matrix = new Matrix();
- // Un-rotate 90 deg.
- matrix.setRotate(270);
- // Translate it back to origin.
- matrix.postTranslate(0, mDisplayInfo.logicalWidth);
- verify(t).setMatrix(eq(app.mSurfaceControl), eq(matrix), any(float[].class));
-
- // Verify we update the position as well.
- final float[] curSurfacePos = {app.mLastSurfacePosition.x, app.mLastSurfacePosition.y};
- matrix.mapPoints(curSurfacePos);
- verify(t).setPosition(eq(app.mSurfaceControl), eq(curSurfacePos[0]), eq(curSurfacePos[1]));
-
- app.finishSeamlessRotation(t);
- assertFalse(app.mSeamlesslyRotated);
- assertNull(app.mPendingSeamlessRotate);
-
- // Simulate the case with deferred layout and animation.
- app.resetSurfacePositionForAnimationLeash(t);
- clearInvocations(t);
- mWm.mWindowPlacerLocked.deferLayout();
- app.updateSurfacePosition(t);
- // Because layout is deferred, the position should keep the reset value.
- assertTrue(app.mLastSurfacePosition.equals(0, 0));
-
- app.seamlesslyRotateIfAllowed(t, ROTATION_0, ROTATION_270, true /* requested */);
- // The last position must be updated so the surface can be unrotated properly.
- assertTrue(app.mLastSurfacePosition.equals(frame.left, frame.top));
- matrix.setRotate(90);
- matrix.postTranslate(mDisplayInfo.logicalHeight, 0);
- curSurfacePos[0] = frame.left;
- curSurfacePos[1] = frame.top;
- matrix.mapPoints(curSurfacePos);
- verify(t).setPosition(eq(app.mSurfaceControl), eq(curSurfacePos[0]), eq(curSurfacePos[1]));
- }
-
- @Test
public void testVisibilityChangeSwitchUser() {
final WindowState window = newWindowBuilder("app", TYPE_APPLICATION).build();
window.mHasSurface = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index a02c3db1e636..8907a72e0b34 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -17,6 +17,8 @@
package com.android.server.wm;
import static android.view.InsetsSource.ID_IME;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -35,16 +37,19 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Configuration;
+import android.graphics.Matrix;
import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.window.WindowContext;
@@ -335,6 +340,31 @@ public class WindowTokenTests extends WindowTestsBase {
}
@Test
+ public void testSeamlesslyRotate() {
+ final SurfaceControl.Transaction t = mTransaction;
+ final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
+ token.mLastSurfacePosition.x = 10;
+ token.mLastSurfacePosition.y = 20;
+ final SeamlessRotator rotator = new SeamlessRotator(ROTATION_0, ROTATION_90,
+ mDisplayContent.getDisplayInfo(), false /* applyFixedTransformationHint */);
+ clearInvocations(t);
+ rotator.unrotate(t, token);
+
+ // Verify surface is un-rotated.
+ final Matrix matrix = new Matrix();
+ // Un-rotate 90 deg.
+ matrix.setRotate(270);
+ // Translate it back to origin.
+ matrix.postTranslate(0, mDisplayInfo.logicalWidth);
+ verify(t).setMatrix(eq(token.mSurfaceControl), eq(matrix), any(float[].class));
+
+ final float[] curSurfacePos = {token.mLastSurfacePosition.x, token.mLastSurfacePosition.y};
+ matrix.mapPoints(curSurfacePos);
+ verify(t).setPosition(eq(token.mSurfaceControl),
+ eq(curSurfacePos[0]), eq(curSurfacePos[1]));
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
public void onDisplayChanged_differentDisplay_reparented() {
final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
index a1d35a7d447c..0749c0b94537 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
@@ -22,7 +22,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -118,7 +118,7 @@ public class WindowTracingLegacyTest {
@Test
public void trace_discared_whenNotTracing() {
mWindowTracing.logState("where");
- verifyZeroInteractions(mWmMock);
+ verifyNoMoreInteractions(mWmMock);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
index 9367941e32a3..3da279bb9663 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
@@ -22,7 +22,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -128,7 +128,7 @@ public class WindowTracingPerfettoTest {
@Test
public void trace_ignoresLogStateCalls_ifTracingIsDisabled() {
sWindowTracing.logState("where");
- verifyZeroInteractions(sWmMock);
+ verifyNoMoreInteractions(sWmMock);
}
@Test
diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
index 7356cdc8889b..42d09cfc2e43 100644
--- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
+++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
@@ -31,7 +31,6 @@ import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsManager;
import android.telephony.satellite.SatelliteManager;
-import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.Preconditions;
@@ -77,9 +76,6 @@ public class TelephonyFrameworkInitializer {
// also check through Compatibility framework a few lines below).
@SuppressWarnings("AndroidFrameworkCompatChange")
private static boolean hasSystemFeature(Context context, String feature) {
- // Check release status of this change in behavior.
- if (!Flags.minimalTelephonyManagersConditionalOnFeatures()) return true;
-
// Check SDK version of the vendor partition.
final int vendorApiLevel = SystemProperties.getInt(
"ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 14d567d141cb..2983e4442a78 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -691,7 +691,7 @@ public class TelephonyManager {
case UNKNOWN:
modemCount = 1;
// check for voice and data support, 0 if not supported
- if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
+ if (!isDeviceVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
modemCount = 0;
}
break;
@@ -2814,7 +2814,7 @@ public class TelephonyManager {
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public int getPhoneType() {
- if (!isVoiceCapable() && !isDataCapable()) {
+ if (!isDeviceVoiceCapable() && !isDataCapable()) {
return PHONE_TYPE_NONE;
}
return getCurrentPhoneType();
diff --git a/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java b/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java
new file mode 100644
index 000000000000..eda5e8613dba
--- /dev/null
+++ b/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
+import static com.android.server.RescueParty.DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN;
+import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
+
+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 static org.mockito.Mockito.spy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.os.RecoverySystem;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.RescueParty.RescuePartyObserver;
+import com.android.server.am.SettingsToPropertiesMapper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Test RescueParty.
+ */
+public class RescuePartyTest {
+ private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
+
+ private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
+ private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
+ private static final String PERSISTENT_PACKAGE = "com.persistent.package";
+ private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package";
+ private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+ "persist.device_config.configuration.disable_rescue_party";
+ private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
+ "persist.device_config.configuration.disable_rescue_party_factory_reset";
+
+ private MockitoSession mSession;
+ private HashMap<String, String> mSystemSettingsMap;
+ private HashMap<String, String> mCrashRecoveryPropertiesMap;
+ //Records the namespaces wiped by setProperties().
+ private HashSet<String> mNamespacesWiped;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mMockContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageWatchdog mMockPackageWatchdog;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private ContentResolver mMockContentResolver;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageManager mPackageManager;
+
+ // Mock only sysprop apis
+ private PackageWatchdog.BootThreshold mSpyBootThreshold;
+
+ @Before
+ public void setUp() throws Exception {
+ mSession =
+ ExtendedMockito.mockitoSession().initMocks(
+ this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
+ .spyStatic(SystemProperties.class)
+ .spyStatic(Settings.Global.class)
+ .spyStatic(Settings.Secure.class)
+ .spyStatic(SettingsToPropertiesMapper.class)
+ .spyStatic(RecoverySystem.class)
+ .spyStatic(RescueParty.class)
+ .spyStatic(PackageWatchdog.class)
+ .startMocking();
+ mSystemSettingsMap = new HashMap<>();
+ mNamespacesWiped = new HashSet<>();
+
+ when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ ApplicationInfo persistentApplicationInfo = new ApplicationInfo();
+ persistentApplicationInfo.flags |=
+ ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT;
+
+ // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and
+ // system. Don't set any flags otherwise.
+ when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE),
+ anyInt())).thenReturn(persistentApplicationInfo);
+ when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE),
+ anyInt())).thenReturn(new ApplicationInfo());
+ // Reset observer instance to get new mock context on every run
+ RescuePartyObserver.reset();
+
+ // Mock SystemProperties setter and various getters
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ String value = invocationOnMock.getArgument(1);
+
+ mSystemSettingsMap.put(key, value);
+ return null;
+ }
+ ).when(() -> SystemProperties.set(anyString(), anyString()));
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ boolean defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
+ }
+ ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ int defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
+ }
+ ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ long defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Long.parseLong(storedValue);
+ }
+ ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
+
+ // Mock DeviceConfig
+ doAnswer((Answer<Boolean>) invocationOnMock -> true)
+ .when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(),
+ anyBoolean()));
+ doAnswer((Answer<Void>) invocationOnMock -> null)
+ .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ DeviceConfig.Properties properties = invocationOnMock.getArgument(0);
+ String namespace = properties.getNamespace();
+ // record a wipe
+ if (properties.getKeyset().isEmpty()) {
+ mNamespacesWiped.add(namespace);
+ }
+ return true;
+ }
+ ).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class)));
+
+ // Mock PackageWatchdog
+ doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
+ .when(() -> PackageWatchdog.getInstance(mMockContext));
+ mockCrashRecoveryProperties(mMockPackageWatchdog);
+
+ doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
+
+ setCrashRecoveryPropRescueBootCount(0);
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSession.finishMocking();
+ }
+
+ @Test
+ public void testBootLoopNoFlags() {
+ // this is old test where the flag needs to be disabled
+ noteBoot(1);
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(2);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testPersistentAppCrashNoFlags() {
+ // this is old test where the flag needs to be disabled
+ noteAppCrash(1, true);
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteAppCrash(2, true);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testIsRecoveryTriggeredReboot() {
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(LEVEL_FACTORY_RESET + 1);
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompleted() {
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ int mitigationCount = LEVEL_FACTORY_RESET + 1;
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(mitigationCount + 1);
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testThrottlingOnBootFailures() {
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long beforeTimeout = now - TimeUnit.MINUTES.toMillis(
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN - 1);
+ setCrashRecoveryPropLastFactoryReset(beforeTimeout);
+ for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i);
+ }
+ assertFalse(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
+ public void testThrottlingOnAppCrash() {
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long beforeTimeout = now - TimeUnit.MINUTES.toMillis(
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN - 1);
+ setCrashRecoveryPropLastFactoryReset(beforeTimeout);
+ for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+ noteAppCrash(i + 1, true);
+ }
+ assertFalse(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
+ public void testNotThrottlingAfterTimeoutOnBootFailures() {
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long afterTimeout = now - TimeUnit.MINUTES.toMillis(
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN + 1);
+ setCrashRecoveryPropLastFactoryReset(afterTimeout);
+ for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i);
+ }
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
+ public void testNotThrottlingAfterTimeoutOnAppCrash() {
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long afterTimeout = now - TimeUnit.MINUTES.toMillis(
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN + 1);
+ setCrashRecoveryPropLastFactoryReset(afterTimeout);
+ for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+ noteAppCrash(i + 1, true);
+ }
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
+ public void testExplicitlyEnablingAndDisablingRescue() {
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+ SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ MITIGATION_RESULT_SKIPPED);
+
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ MITIGATION_RESULT_SUCCESS);
+ }
+
+ @Test
+ public void testDisablingRescueByDeviceConfigFlag() {
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
+
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ MITIGATION_RESULT_SKIPPED);
+
+ // Restore the property value initialized in SetUp()
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
+ }
+
+ @Test
+ public void testDisablingFactoryResetByDeviceConfigFlag() {
+ SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true));
+
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+
+ // Restore the property value initialized in SetUp()
+ SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, "");
+ }
+
+ @Test
+ public void testHealthCheckLevelsNoFlags() {
+ // this is old test where the flag needs to be disabled
+ RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+ // Ensure that no action is taken for cases where the failure reason is unknown
+ assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+
+ // Ensure the correct user impact is returned for each mitigation count.
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
+ }
+
+ @Test
+ public void testBootLoopLevelsNoFlags() {
+ RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+ assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+ assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
+ }
+
+
+ private void noteBoot(int mitigationCount) {
+ RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount);
+ }
+
+ private void noteAppCrash(int mitigationCount, boolean isPersistent) {
+ String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
+ RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ new VersionedPackage(packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH,
+ mitigationCount);
+ }
+
+ // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions
+ private void mockCrashRecoveryProperties(PackageWatchdog watchdog) {
+ // mock properties in RescueParty
+ try {
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.attempting_factory_reset", "false");
+ return Boolean.parseBoolean(storedValue);
+ }).when(() -> RescueParty.isFactoryResetPropertySet());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ boolean value = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_factory_reset",
+ Boolean.toString(value));
+ return null;
+ }).when(() -> RescueParty.setFactoryResetProperty(anyBoolean()));
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.attempting_reboot", "false");
+ return Boolean.parseBoolean(storedValue);
+ }).when(() -> RescueParty.isRebootPropertySet());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ boolean value = invocationOnMock.getArgument(0);
+ setCrashRecoveryPropAttemptingReboot(value);
+ return null;
+ }).when(() -> RescueParty.setRebootProperty(anyBoolean()));
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("persist.crashrecovery.last_factory_reset", "0");
+ return Long.parseLong(storedValue);
+ }).when(() -> RescueParty.getLastFactoryResetTimeMs());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long value = invocationOnMock.getArgument(0);
+ setCrashRecoveryPropLastFactoryReset(value);
+ return null;
+ }).when(() -> RescueParty.setLastFactoryResetTimeMs(anyLong()));
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.max_rescue_level_attempted", "0");
+ return Integer.parseInt(storedValue);
+ }).when(() -> RescueParty.getMaxRescueLevelAttempted());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int value = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.max_rescue_level_attempted",
+ Integer.toString(value));
+ return null;
+ }).when(() -> RescueParty.setMaxRescueLevelAttempted(anyInt()));
+
+ } catch (Exception e) {
+ // tests will fail, just printing the error
+ System.out.println("Error while mocking crashrecovery properties " + e.getMessage());
+ }
+
+ // mock properties in BootThreshold
+ try {
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
+ mCrashRecoveryPropertiesMap = new HashMap<>();
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.rescue_boot_count", "0");
+ return Integer.parseInt(storedValue);
+ }).when(mSpyBootThreshold).getCount();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int count = invocationOnMock.getArgument(0);
+ setCrashRecoveryPropRescueBootCount(count);
+ return null;
+ }).when(mSpyBootThreshold).setCount(anyInt());
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.boot_mitigation_count", "0");
+ return Integer.parseInt(storedValue);
+ }).when(mSpyBootThreshold).getMitigationCount();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_count",
+ Integer.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setMitigationCount(anyInt());
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.rescue_boot_start", "0");
+ return Long.parseLong(storedValue);
+ }).when(mSpyBootThreshold).getStart();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_start",
+ Long.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setStart(anyLong());
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.boot_mitigation_start", "0");
+ return Long.parseLong(storedValue);
+ }).when(mSpyBootThreshold).getMitigationStart();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_start",
+ Long.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setMitigationStart(anyLong());
+
+ Field mBootThresholdField = watchdog.getClass().getDeclaredField("mBootThreshold");
+ mBootThresholdField.setAccessible(true);
+ mBootThresholdField.set(watchdog, mSpyBootThreshold);
+ } catch (Exception e) {
+ // tests will fail, just printing the error
+ System.out.println("Error while spying BootThreshold " + e.getMessage());
+ }
+ }
+
+ private void setCrashRecoveryPropRescueBootCount(int count) {
+ mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count",
+ Integer.toString(count));
+ }
+
+ private void setCrashRecoveryPropAttemptingReboot(boolean value) {
+ mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_reboot",
+ Boolean.toString(value));
+ }
+
+ private void setCrashRecoveryPropLastFactoryReset(long value) {
+ mCrashRecoveryPropertiesMap.put("persist.crashrecovery.last_factory_reset",
+ Long.toString(value));
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
index 0c3c7e2af6f2..e868a6cc5a80 100644
--- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -34,7 +34,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
/**
* Test for testing revokeTrust & grantTrust for non-renewable trust.
@@ -120,7 +120,7 @@ class GrantAndRevokeTrustTest {
trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback)
await()
- verifyZeroInteractions(callback)
+ verifyNoMoreInteractions(callback)
}
companion object {
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index f5d4b0c5e345..cc7eebca1d00 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -33,7 +33,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -120,7 +120,7 @@ public class UsbServiceTest {
verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
enable, TEST_TRANSACTION_ID, mCallback, null);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
clearInvocations(mUsbPortManager);
clearInvocations(mCallback);
@@ -131,7 +131,7 @@ public class UsbServiceTest {
assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
- verifyZeroInteractions(mUsbPortManager);
+ verifyNoMoreInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
clearInvocations(mUsbPortManager);
@@ -188,7 +188,7 @@ public class UsbServiceTest {
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
mCallback, TEST_SECOND_CALLER_ID, false);
- verifyZeroInteractions(mUsbPortManager);
+ verifyNoMoreInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
}
@@ -203,7 +203,7 @@ public class UsbServiceTest {
verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
mCallback, null);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
/**