summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp16
-rw-r--r--TEST_MAPPING10
-rw-r--r--apct-tests/perftests/protolog/Android.bp33
-rw-r--r--apct-tests/perftests/protolog/AndroidManifest.xml31
-rw-r--r--apct-tests/perftests/protolog/AndroidTest.xml67
-rw-r--r--apct-tests/perftests/protolog/OWNERS1
-rw-r--r--apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java171
-rw-r--r--api/StubLibraries.bp2
-rw-r--r--core/api/current.txt7
-rw-r--r--core/api/system-current.txt17
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/app/ActivityManager.java14
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/ApplicationStartInfo.java34
-rw-r--r--core/java/android/app/SystemServiceRegistry.java17
-rw-r--r--core/java/android/app/activity_manager.aconfig11
-rw-r--r--core/java/android/app/admin/DevicePolicyIdentifiers.java3
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java5
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig1
-rw-r--r--core/java/android/app/supervision/ISupervisionManager.aidl25
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java57
-rw-r--r--core/java/android/app/supervision/flags.aconfig10
-rw-r--r--core/java/android/companion/virtual/ActivityPolicyExemption.java1
-rw-r--r--core/java/android/content/Context.java17
-rw-r--r--core/java/android/content/Intent.java10
-rw-r--r--core/java/android/content/res/ColorStateList.java2
-rw-r--r--core/java/android/content/res/ComplexColor.java3
-rw-r--r--core/java/android/content/res/Resources.java4
-rw-r--r--core/java/android/content/res/ResourcesImpl.java2
-rw-r--r--core/java/android/content/res/TypedArray.java2
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java10
-rw-r--r--core/java/android/hardware/radio/flags.aconfig8
-rw-r--r--core/java/android/os/BatteryStats.java4
-rw-r--r--core/java/android/os/Binder.java7
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java63
-rw-r--r--core/java/android/os/Process.java7
-rw-r--r--core/java/android/service/dreams/DreamOverlayService.java45
-rw-r--r--core/java/android/service/dreams/DreamService.java97
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java17
-rw-r--r--core/java/android/text/flags/flags.aconfig7
-rw-r--r--core/java/android/util/LruCache.java7
-rw-r--r--core/java/android/util/StateSet.java3
-rw-r--r--core/java/android/view/Display.java2
-rw-r--r--core/java/android/view/HandwritingInitiator.java4
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java24
-rw-r--r--core/java/android/view/InsetsAnimationSpec.java39
-rw-r--r--core/java/android/view/InsetsAnimationThreadControlRunner.java7
-rw-r--r--core/java/android/view/InsetsController.java95
-rw-r--r--core/java/android/view/InsetsResizeAnimationRunner.java10
-rw-r--r--core/java/android/view/MotionEvent.java29
-rw-r--r--core/java/android/view/WindowInsetsAnimationController.java13
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java167
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java4
-rw-r--r--core/java/android/webkit/WebSettings.java23
-rw-r--r--core/java/android/widget/TextView.java2
-rw-r--r--core/java/android/window/IBackAnimationRunner.aidl9
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig10
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig10
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java36
-rw-r--r--core/java/com/android/internal/accessibility/util/ShortcutUtils.java30
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java9
-rw-r--r--core/java/com/android/internal/protolog/IProtoLogClient.aidl2
-rw-r--r--core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl (renamed from core/java/com/android/internal/protolog/IProtoLogService.aidl)2
-rw-r--r--core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java26
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java24
-rw-r--r--core/java/com/android/internal/protolog/ProtoLog.java5
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogCommandHandler.java25
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogConfigurationService.java (renamed from core/java/com/android/internal/protolog/ProtoLogService.java)12
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogDataSource.java117
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl5
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarIcon.java24
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java40
-rw-r--r--core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java5
-rw-r--r--core/jni/android_graphics_SurfaceTexture.cpp37
-rw-r--r--core/proto/android/app/appstartinfo.proto1
-rw-r--r--core/proto/android/server/activitymanagerservice.proto4
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/res/res/values/config_telephony.xml16
-rw-r--r--core/res/res/values/config_tv_external_input_logging.xml34
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml18
-rw-r--r--core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml18
-rw-r--r--core/tests/coretests/src/android/util/StateSetTest.java2
-rw-r--r--core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java17
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java129
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java2
-rw-r--r--core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java57
-rw-r--r--data/fonts/Android.bp32
-rw-r--r--data/fonts/alias.json37
-rw-r--r--data/fonts/fallback_order.json136
-rw-r--r--libs/WindowManager/Shell/Android.bp4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl)4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java)6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl)2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt)2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java)4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java161
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt59
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt49
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.kt49
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java40
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt2
-rw-r--r--libs/hwui/FeatureFlags.h9
-rw-r--r--libs/hwui/hwui/MinikinUtils.h49
-rw-r--r--libs/hwui/tests/common/TestContext.cpp12
-rw-r--r--media/java/android/media/AudioManager.java18
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/tv/tuner/Tuner.java46
-rw-r--r--media/jni/android_media_ImageReader.cpp57
-rw-r--r--media/mca/filterfw/native/core/gl_env.cpp26
-rw-r--r--media/tests/mediatestutils/Android.bp2
-rw-r--r--media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java59
-rw-r--r--packages/CompanionDeviceManager/res/layout/activity_confirmation.xml3
-rw-r--r--packages/CompanionDeviceManager/res/values/styles.xml5
-rw-r--r--packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java50
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java20
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java9
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS1
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java35
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig10
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig37
-rw-r--r--packages/SystemUI/animation/build.gradle11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt12
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt29
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt33
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt5
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt144
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt15
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt220
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt12
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt103
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt148
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt41
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt138
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt310
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt34
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt46
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt140
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt17
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt15
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt7
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt10
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt527
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt15
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt1
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt13
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt111
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt8
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/SystemActionsTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/InputSessionTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackTransformationTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatterySpecsTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraIntentsTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ClassifierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DiagonalClassifierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/HistoryTrackerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ProximityClassifierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/SingleTapClassifierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TypeClassifierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java (renamed from packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt)138
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt71
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt143
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationUtilsTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationUtilsTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/CustomIconCacheTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AllModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AppAdapterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/FavoritesModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/StartActivityData.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/coroutines/FlowTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt131
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt157
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt107
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt328
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt110
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt84
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt399
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt85
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt30
-rw-r--r--packages/SystemUI/res/layout/notification_template_en_route_contracted.xml29
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/config.xml2
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json81
-rw-r--r--packages/SystemUI/shared/Android.bp2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt138
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt176
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/OWNERS1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt494
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt311
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt530
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt156
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt160
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt148
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java2
-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.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java104
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt)52
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt367
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt95
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt278
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt116
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt467
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt165
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt168
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt129
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/leak/ReferenceTestUtils.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/leak/ReferenceTestUtils.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java8
-rw-r--r--packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java6
-rw-r--r--ravenwood/Android.bp2
-rw-r--r--ravenwood/TEST_MAPPING5
-rw-r--r--ravenwood/bivalenttest/Android.bp6
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java110
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java93
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java52
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java136
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java (renamed from ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java)41
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java (renamed from ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java)22
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java75
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java79
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java93
-rw-r--r--ravenwood/coretest/README.md3
-rw-r--r--ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java53
-rw-r--r--ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java51
-rw-r--r--ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java51
-rw-r--r--ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java51
-rw-r--r--ravenwood/empty-res/Android.bp4
-rw-r--r--ravenwood/empty-res/AndroidManifest.xml19
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java77
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java124
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java355
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java20
-rw-r--r--ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java49
-rw-r--r--ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java36
-rw-r--r--ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java3
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java2
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java49
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java (renamed from ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java)5
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java4
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java26
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java2
-rw-r--r--ravenwood/runtime-jni/ravenwood_runtime.cpp2
-rw-r--r--ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java5
-rw-r--r--ravenwood/texts/ravenwood-annotation-allowed-classes.txt3
-rw-r--r--ravenwood/texts/ravenwood-framework-policies.txt7
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt23
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt11
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt57
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt453
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt40
-rw-r--r--services/Android.bp2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java48
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java4
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/SerialService.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/Android.bp7
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java63
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java4
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java39
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java14
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java22
-rw-r--r--services/core/java/com/android/server/audio/AudioServerPermissionProvider.java4
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java120
-rw-r--r--services/core/java/com/android/server/audio/HardeningEnforcer.java52
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java29
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java11
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java32
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java13
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java11
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java16
-rw-r--r--services/core/java/com/android/server/display/mode/Vote.java22
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java30
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java7
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java233
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java27
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java9
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java33
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java10
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java6
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java15
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java6
-rw-r--r--services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java6
-rw-r--r--services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java43
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java29
-rw-r--r--services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java22
-rw-r--r--services/core/java/com/android/server/stats/stats_flags.aconfig14
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java11
-rw-r--r--services/core/java/com/android/server/tracing/TracingServiceProxy.java17
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java263
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java298
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java84
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java14
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java13
-rw-r--r--services/core/java/com/android/server/wm/ActionChain.java239
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java445
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java34
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java10
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java439
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java5
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java9
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java43
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java5
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java125
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java10
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java52
-rw-r--r--services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java1
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java360
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java1382
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java9
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java58
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java49
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java18
-rw-r--r--services/core/java/com/android/server/wm/Transition.java32
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java9
-rw-r--r--services/core/java/com/android/server/wm/TransparentPolicy.java4
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java19
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowContainerThumbnail.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java114
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java152
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowTracingDataSource.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowTracingPerfetto.java7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java168
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java9
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java29
-rw-r--r--services/java/com/android/server/SystemServer.java14
-rw-r--r--services/supervision/Android.bp (renamed from ravenwood/coretest/Android.bp)23
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java67
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java65
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java88
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java61
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java28
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java174
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java164
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java121
-rw-r--r--services/tests/vibrator/Android.bp2
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java551
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java358
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java193
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java62
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java834
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java65
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java81
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java68
-rw-r--r--telecomm/java/android/telecom/PhoneAccount.java7
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java1
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java4
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt3
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt3
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt3
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt3
-rw-r--r--tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java32
-rw-r--r--tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java89
-rw-r--r--tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java (renamed from tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java)70
-rw-r--r--tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt13
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java20
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl1
788 files changed, 18108 insertions, 9549 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b4127c5660f7..3c5686bd6d13 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -26,6 +26,7 @@ aconfig_declarations_group {
"android.app.flags-aconfig-java",
"android.app.ondeviceintelligence-aconfig-java",
"android.app.smartspace.flags-aconfig-java",
+ "android.app.supervision.flags-aconfig-java",
"android.app.usage.flags-aconfig-java",
"android.app.wearable.flags-aconfig-java",
"android.appwidget.flags-aconfig-java",
@@ -1212,6 +1213,21 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Supervision
+aconfig_declarations {
+ name: "android.app.supervision.flags-aconfig",
+ exportable: true,
+ package: "android.app.supervision.flags",
+ container: "system",
+ srcs: ["core/java/android/app/supervision/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.app.supervision.flags-aconfig-java",
+ aconfig_declarations: "android.app.supervision.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// SurfaceFlinger
java_aconfig_library {
name: "surfaceflinger_flags_java_lib",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 5db077220b20..dfacbc425181 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -140,11 +140,17 @@
"ravenwood-presubmit": [
{
"name": "CtsUtilTestCasesRavenwood",
- "host": true
+ "host": true,
+ "file_patterns": [
+ "[Rr]avenwood"
+ ]
},
{
"name": "RavenwoodBivalentTest",
- "host": true
+ "host": true,
+ "file_patterns": [
+ "[Rr]avenwood"
+ ]
}
],
"postsubmit-managedprofile-stress": [
diff --git a/apct-tests/perftests/protolog/Android.bp b/apct-tests/perftests/protolog/Android.bp
new file mode 100644
index 000000000000..08e365be514a
--- /dev/null
+++ b/apct-tests/perftests/protolog/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+ name: "ProtologPerfTests",
+ team: "trendy_team_windowing_tools",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.annotation_annotation",
+ "apct-perftests-utils",
+ "collector-device-lib",
+ "platform-test-annotations",
+ ],
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+ data: [":perfetto_artifacts"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/apct-tests/perftests/protolog/AndroidManifest.xml b/apct-tests/perftests/protolog/AndroidManifest.xml
new file mode 100644
index 000000000000..68125df99ec3
--- /dev/null
+++ b/apct-tests/perftests/protolog/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.perftests.protolog">
+
+ <!-- For perfetto trace files -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.perftests.protolog">
+ <!-- <meta-data android:name="listener" android:value="android.protolog.ProtologPerfRunListener" /> -->
+ </instrumentation>
+</manifest>
diff --git a/apct-tests/perftests/protolog/AndroidTest.xml b/apct-tests/perftests/protolog/AndroidTest.xml
new file mode 100644
index 000000000000..871a20ce4cef
--- /dev/null
+++ b/apct-tests/perftests/protolog/AndroidTest.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs ProtologPerfTests metric instrumentation.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-metric-instrumentation" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="ProtologPerfTests.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="force-skip-system-props" value="true" />
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="cmd window dismiss-keyguard" />
+ <option name="run-command" value="cmd package compile -m speed com.android.perftests.wm" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+ </target_preparer>
+
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.perftests.protolog" />
+ <option name="hidden-api-checks" value="false"/>
+
+ <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+ <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+
+ <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ <!-- ProcLoadListener related arguments -->
+ <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+ <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+ <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+ <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+ <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+ </test>
+
+ <!-- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/local/tmp/ProtologPerfTests" /> -->
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <!-- <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector> -->
+</configuration>
diff --git a/apct-tests/perftests/protolog/OWNERS b/apct-tests/perftests/protolog/OWNERS
new file mode 100644
index 000000000000..3f3308cfc75a
--- /dev/null
+++ b/apct-tests/perftests/protolog/OWNERS
@@ -0,0 +1 @@
+include platform/development:/tools/winscope/OWNERS
diff --git a/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java b/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java
new file mode 100644
index 000000000000..e1edb3712ff0
--- /dev/null
+++ b/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.protolog;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogLevel;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public class ProtologPerfTest {
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Parameters(name="logToProto_{0}_logToLogcat_{1}")
+ public static Collection<Object[]> params() {
+ return Arrays.asList(new Object[][] {
+ { true, true },
+ { true, false },
+ { false, true },
+ { false, false }
+ });
+ }
+
+ private final boolean mLogToProto;
+ private final boolean mLogToLogcat;
+
+ public ProtologPerfTest(boolean logToProto, boolean logToLogcat) {
+ mLogToProto = logToProto;
+ mLogToLogcat = logToLogcat;
+ }
+
+ @BeforeClass
+ public static void init() {
+ ProtoLog.init(TestProtoLogGroup.values());
+ }
+
+ @Before
+ public void setUp() {
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(mLogToProto);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(mLogToLogcat);
+ }
+
+ @Test
+ public void logProcessedProtoLogMessageWithoutArgs() {
+ final var protoLog = ProtoLog.getSingleInstance();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ protoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
+ 0, (Object[]) null);
+ }
+ }
+
+ @Test
+ public void logProcessedProtoLogMessageWithArgs() {
+ final var protoLog = ProtoLog.getSingleInstance();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ protoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
+ 0b1110101001010100,
+ new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
+ }
+ }
+
+ @Test
+ public void logNonProcessedProtoLogMessageWithNoArgs() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message");
+ }
+ }
+
+ @Test
+ public void logNonProcessedProtoLogMessageWithArgs() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test messag %s, %d, %b", "arg1", 2, true);
+ }
+ }
+
+ private enum TestProtoLogGroup implements IProtoLogGroup {
+ TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+
+ /**
+ * @param enabled set to false to exclude all log statements for this group from
+ * compilation, they will not be available in runtime.
+ * @param logToProto enable binary logging for the group
+ * @param logToLogcat enable text logging for the group
+ * @param tag name of the source of the logged message
+ */
+ TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return ordinal();
+ }
+ }
+}
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index d991da59f167..b3a674fbd70e 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -890,7 +890,7 @@ java_genrule {
cmd: "rm -f $(genDir)/framework.aidl.merged && " +
"for i in $(in); do " +
" rm -f $(genDir)/framework.aidl.tmp && " +
- " $(location sdkparcelables) $$i $(genDir)/framework.aidl.tmp && " +
+ " $(location sdkparcelables) $$i $(genDir)/framework.aidl.tmp --guarantee_stable && " +
" cat $(genDir)/framework.aidl.tmp >> $(genDir)/framework.aidl.merged; " +
"done && " +
"sort -u $(genDir)/framework.aidl.merged > $(out)",
diff --git a/core/api/current.txt b/core/api/current.txt
index c7df6623e36d..861be4079acc 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7968,7 +7968,7 @@ package android.app.admin {
field public static final String PERMISSION_GRANT_POLICY = "permissionGrant";
field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity";
field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken";
- field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String SECURITY_LOGGING_POLICY = "securityLogging";
+ field public static final String SECURITY_LOGGING_POLICY = "securityLogging";
field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled";
field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages";
@@ -54922,6 +54922,8 @@ package android.view.accessibility {
method @Deprecated public void addAction(int);
method public void addChild(android.view.View);
method public void addChild(android.view.View, int);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int);
method public boolean canOpenPopup();
method public int describeContents();
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
@@ -54950,6 +54952,7 @@ package android.view.accessibility {
method public int getInputType();
method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
method public int getLiveRegion();
method public int getMaxTextLength();
method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
@@ -55010,6 +55013,8 @@ package android.view.accessibility {
method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
method public boolean removeChild(android.view.View);
method public boolean removeChild(android.view.View, int);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View);
+ method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int);
method public void setAccessibilityDataSensitive(boolean);
method public void setAccessibilityFocused(boolean);
method public void setAvailableExtraData(java.util.List<java.lang.String>);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 539962fef236..5413c6606bcb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -201,7 +201,7 @@ package android {
field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS";
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
- field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
+ field public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
@@ -1296,7 +1296,7 @@ package android.app.admin {
}
public final class DevicePolicyIdentifiers {
- field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String AUDIT_LOGGING_POLICY = "auditLogging";
+ field public static final String AUDIT_LOGGING_POLICY = "auditLogging";
}
public class DevicePolicyKeyguardService extends android.app.Service {
@@ -1308,7 +1308,7 @@ package android.app.admin {
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
@@ -1328,7 +1328,7 @@ package android.app.admin {
method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
method public boolean isDeviceManaged();
method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE) public boolean isDevicePotentiallyStolen();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
@@ -1344,8 +1344,8 @@ package android.app.admin {
method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
@@ -3443,7 +3443,7 @@ package android.companion.virtual {
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.ActivityPolicyExemption> CREATOR;
}
- public static final class ActivityPolicyExemption.Builder {
+ @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final class ActivityPolicyExemption.Builder {
ctor public ActivityPolicyExemption.Builder();
method @NonNull public android.companion.virtual.ActivityPolicyExemption build();
method @NonNull public android.companion.virtual.ActivityPolicyExemption.Builder setComponentName(@NonNull android.content.ComponentName);
@@ -8087,6 +8087,7 @@ package android.media.tv.tuner {
public class Tuner implements java.lang.AutoCloseable {
ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @Nullable String, int);
method public int applyFrontend(@NonNull android.media.tv.tuner.frontend.FrontendInfo);
+ method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @RequiresPermission(allOf={"android.permission.TUNER_RESOURCE_ACCESS", "android.permission.ACCESS_TV_TUNER"}) public int applyFrontendByType(int);
method public int cancelScanning();
method public int cancelTuning();
method public void clearOnTuneEventListener();
@@ -14223,7 +14224,7 @@ package android.telecom {
field public static final int CAPABILITY_EMERGENCY_PREFERRED = 8192; // 0x2000
field public static final int CAPABILITY_EMERGENCY_VIDEO_CALLING = 512; // 0x200
field public static final int CAPABILITY_MULTI_USER = 32; // 0x20
- field public static final String EXTRA_PLAY_CALL_RECORDING_TONE = "android.telecom.extra.PLAY_CALL_RECORDING_TONE";
+ field @Deprecated @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EXTRA_PLAY_CALL_RECORDING_TONE = "android.telecom.extra.PLAY_CALL_RECORDING_TONE";
field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EXTRA_SKIP_CALL_FILTERING = "android.telecom.extra.SKIP_CALL_FILTERING";
field public static final String EXTRA_SORT_ORDER = "android.telecom.extra.SORT_ORDER";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ce0d38ffe9ad..ec23cfe7c6bb 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1992,6 +1992,7 @@ package android.media {
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice();
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isVolumeControlUsingVolumeGroups();
+ method public void permissionUpdateBarrier();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setCsd(float);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setNotifAliasRingForTest(boolean);
@@ -3676,6 +3677,7 @@ package android.view {
public static final class Display.Mode implements android.os.Parcelable {
ctor public Display.Mode(int, int, float);
+ method public boolean isSynthetic();
method public boolean matches(int, int, float);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e57630bfd5ed..68063c4a1a50 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -233,6 +233,10 @@ public class ActivityManager {
private static final RateLimitingCache<List<RunningAppProcessInfo>> mRunningProcessesCache =
new RateLimitingCache<>(10, 4);
+ /** Rate-Limiting Cache that allows no more than 200 calls to the service per second. */
+ private static final RateLimitingCache<List<ProcessErrorStateInfo>> mErrorProcessesCache =
+ new RateLimitingCache<>(10, 2);
+
/**
* Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
* Will be called when a Uid has become frozen or unfrozen.
@@ -3685,6 +3689,16 @@ public class ActivityManager {
* specified.
*/
public List<ProcessErrorStateInfo> getProcessesInErrorState() {
+ if (Flags.rateLimitGetProcessesInErrorState()) {
+ return mErrorProcessesCache.get(() -> {
+ return getProcessesInErrorStateInternal();
+ });
+ } else {
+ return getProcessesInErrorStateInternal();
+ }
+ }
+
+ private List<ProcessErrorStateInfo> getProcessesInErrorStateInternal() {
try {
return getService().getProcessesInErrorState();
} catch (RemoteException e) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d4558533291f..4350545a1b1d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4583,7 +4583,7 @@ public final class ActivityThread extends ClientTransactionHandler
public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
@Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
@NonNull SurfaceControl startingWindowLeash) {
- final DecorView decorView = (DecorView) r.window.peekDecorView();
+ final DecorView decorView = r.window != null ? (DecorView) r.window.peekDecorView() : null;
if (parcelable != null && decorView != null) {
createSplashScreen(r, decorView, parcelable, startingWindowLeash);
} else {
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 0ff55149bf6e..1e9a79bca65a 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -210,6 +210,11 @@ public final class ApplicationStartInfo implements Parcelable {
public static final int START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE = 7;
/**
+ * @see #getMonoticCreationTimeMs
+ */
+ private long mMonoticCreationTimeMs;
+
+ /**
* @see #getStartupState
*/
private @StartupState int mStartupState;
@@ -487,6 +492,15 @@ public final class ApplicationStartInfo implements Parcelable {
}
/**
+ * Monotonic elapsed time persisted across reboots.
+ *
+ * @hide
+ */
+ public long getMonoticCreationTimeMs() {
+ return mMonoticCreationTimeMs;
+ }
+
+ /**
* The process id.
*
* <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
@@ -669,7 +683,9 @@ public final class ApplicationStartInfo implements Parcelable {
}
/** @hide */
- public ApplicationStartInfo() {}
+ public ApplicationStartInfo(long monotonicCreationTimeMs) {
+ mMonoticCreationTimeMs = monotonicCreationTimeMs;
+ }
/** @hide */
public ApplicationStartInfo(ApplicationStartInfo other) {
@@ -686,6 +702,7 @@ public final class ApplicationStartInfo implements Parcelable {
mStartIntent = other.mStartIntent;
mLaunchMode = other.mLaunchMode;
mWasForceStopped = other.mWasForceStopped;
+ mMonoticCreationTimeMs = other.mMonoticCreationTimeMs;
}
private ApplicationStartInfo(@NonNull Parcel in) {
@@ -708,6 +725,7 @@ public final class ApplicationStartInfo implements Parcelable {
in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
mLaunchMode = in.readInt();
mWasForceStopped = in.readBoolean();
+ mMonoticCreationTimeMs = in.readLong();
}
private static String intern(@Nullable String source) {
@@ -786,6 +804,7 @@ public final class ApplicationStartInfo implements Parcelable {
}
proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
+ proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonoticCreationTimeMs);
proto.end(token);
}
@@ -869,6 +888,10 @@ public final class ApplicationStartInfo implements Parcelable {
mWasForceStopped = proto.readBoolean(
ApplicationStartInfoProto.WAS_FORCE_STOPPED);
break;
+ case (int) ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS:
+ mMonoticCreationTimeMs = proto.readLong(
+ ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS);
+ break;
}
}
proto.end(token);
@@ -881,6 +904,8 @@ public final class ApplicationStartInfo implements Parcelable {
sb.append(prefix)
.append("ApplicationStartInfo ").append(seqSuffix).append(':')
.append('\n')
+ .append(" monotonicCreationTimeMs=").append(mMonoticCreationTimeMs)
+ .append('\n')
.append(" pid=").append(mPid)
.append(" realUid=").append(mRealUid)
.append(" packageUid=").append(mPackageUid)
@@ -949,14 +974,15 @@ public final class ApplicationStartInfo implements Parcelable {
&& mDefiningUid == o.mDefiningUid && mReason == o.mReason
&& mStartupState == o.mStartupState && mStartType == o.mStartType
&& mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
- && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped;
+ && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped
+ && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs;
}
@Override
public int hashCode() {
return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
- mStartType, mLaunchMode, mProcessName,
- mStartupTimestampsNs);
+ mStartType, mLaunchMode, mProcessName, mStartupTimestampsNs,
+ mMonoticCreationTimeMs);
}
private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index cb38cf297cf6..8b3ee24db025 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -48,6 +48,8 @@ import android.app.sdksandbox.SdkSandboxManagerFrameworkInitializer;
import android.app.search.SearchUiManager;
import android.app.slice.SliceManager;
import android.app.smartspace.SmartspaceManager;
+import android.app.supervision.ISupervisionManager;
+import android.app.supervision.SupervisionManager;
import android.app.time.TimeManager;
import android.app.timedetector.TimeDetector;
import android.app.timedetector.TimeDetectorImpl;
@@ -1703,6 +1705,21 @@ public final class SystemServiceRegistry {
return new E2eeContactKeysManager(ctx);
}});
+ registerService(Context.SUPERVISION_SERVICE, SupervisionManager.class,
+ new CachedServiceFetcher<>() {
+ @Override
+ public SupervisionManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ if (!android.app.supervision.flags.Flags.supervisionApi()) {
+ throw new ServiceNotFoundException(
+ "SupervisionManager is not supported");
+ }
+ IBinder iBinder = ServiceManager.getServiceOrThrow(
+ Context.SUPERVISION_SERVICE);
+ ISupervisionManager service = ISupervisionManager.Stub.asInterface(iBinder);
+ return new SupervisionManager(ctx, service);
+ }
+ });
// DO NOT do a flag check like this unless the flag is read-only.
// (because this code is executed during preload in zygote.)
// If the flag is mutable, the check should be inside CachedServiceFetcher.
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index d9594d3e4c31..32e6e80cc3e9 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -93,3 +93,14 @@ flag {
}
}
+flag {
+ namespace: "backstage_power"
+ name: "rate_limit_get_processes_in_error_state"
+ description: "Rate limit calls to getProcessesInErrorState using a cache"
+ is_fixed_read_only: true
+ bug: "361146083"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index eeaf0b3706fc..156512a90295 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -17,7 +17,6 @@
package android.app.admin;
import static android.app.admin.flags.Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -50,7 +49,6 @@ public final class DevicePolicyIdentifiers {
/**
* String identifier for {@link DevicePolicyManager#setSecurityLoggingEnabled}.
*/
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
public static final String SECURITY_LOGGING_POLICY = "securityLogging";
/**
@@ -58,7 +56,6 @@ public final class DevicePolicyIdentifiers {
*
* @hide
*/
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@SystemApi
public static final String AUDIT_LOGGING_POLICY = "auditLogging";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ba1dc5677b21..5088ea6b603c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -60,7 +60,6 @@ import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_PROVISIONING_FIX_ENABLED;
import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
@@ -14335,7 +14334,6 @@ public class DevicePolicyManager {
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void setAuditLogEnabled(boolean enabled) {
throwIfParentInstance("setAuditLogEnabled");
@@ -14352,7 +14350,6 @@ public class DevicePolicyManager {
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public boolean isAuditLogEnabled() {
throwIfParentInstance("isAuditLogEnabled");
@@ -14374,7 +14371,6 @@ public class DevicePolicyManager {
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void setAuditLogEventCallback(
@NonNull @CallbackExecutor Executor executor,
@@ -14401,7 +14397,6 @@ public class DevicePolicyManager {
* @hide
*/
@SystemApi
- @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void clearAuditLogEventCallback() {
throwIfParentInstance("clearAuditLogEventCallback");
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 9148e3c3a072..56f47922b078 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -105,6 +105,7 @@ flag {
bug: "289520697"
}
+# Fully rolled out and must not be used.
flag {
name: "security_log_v2_enabled"
is_exported: true
diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl
new file mode 100644
index 000000000000..8d25cad2fc67
--- /dev/null
+++ b/core/java/android/app/supervision/ISupervisionManager.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.supervision;
+
+/**
+ * Internal IPC interface to the supervision service.
+ * {@hide}
+ */
+interface ISupervisionManager {
+ boolean isSupervisionEnabled();
+}
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
new file mode 100644
index 000000000000..8611a92074c0
--- /dev/null
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.supervision;
+
+import android.annotation.SystemService;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.RemoteException;
+
+/**
+ * Service for handling parental supervision.
+ *
+ * @hide
+ */
+@SystemService(Context.SUPERVISION_SERVICE)
+public class SupervisionManager {
+ private final Context mContext;
+ private final ISupervisionManager mService;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public SupervisionManager(Context context, ISupervisionManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns whether the device is supervised.
+ *
+ * @hide
+ */
+ public boolean isSupervisionEnabled() {
+ try {
+ return mService.isSupervisionEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+}
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
new file mode 100644
index 000000000000..bcb5b3636c95
--- /dev/null
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -0,0 +1,10 @@
+package: "android.app.supervision.flags"
+container: "system"
+
+flag {
+ name: "supervision_api"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag to enable the SupervisionService"
+ bug: "340351729"
+} \ No newline at end of file
diff --git a/core/java/android/companion/virtual/ActivityPolicyExemption.java b/core/java/android/companion/virtual/ActivityPolicyExemption.java
index c81bb43ac1d3..dc285d4c08b7 100644
--- a/core/java/android/companion/virtual/ActivityPolicyExemption.java
+++ b/core/java/android/companion/virtual/ActivityPolicyExemption.java
@@ -118,6 +118,7 @@ public final class ActivityPolicyExemption implements Parcelable {
/**
* Builder for {@link ActivityPolicyExemption}.
*/
+ @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
public static final class Builder {
private @Nullable ComponentName mComponentName;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ffcb1cbec94e..3bf0f0324716 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -106,6 +106,7 @@ import android.window.WindowContext;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.compat.IPlatformCompatNative;
+import com.android.internal.protolog.ProtoLogConfigurationService;
import java.io.File;
import java.io.FileInputStream;
@@ -6701,13 +6702,23 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve the
- * {@link com.android.internal.protolog.ProtoLogService} for registering ProtoLog clients.
+ * {@link ProtoLogConfigurationService} for registering ProtoLog clients.
*
* @see #getSystemService(String)
- * @see com.android.internal.protolog.ProtoLogService
+ * @see ProtoLogConfigurationService
* @hide
*/
- public static final String PROTOLOG_SERVICE = "protolog";
+ public static final String PROTOLOG_CONFIGURATION_SERVICE = "protolog_configuration";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.supervision.SupervisionManager}.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.supervision.SupervisionManager
+ * @hide
+ */
+ public static final String SUPERVISION_SERVICE = "supervision";
/**
* Determine whether the given permission is allowed for a particular
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index cb57c7bd565d..abb0d8d88cce 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3767,7 +3767,7 @@ public class Intent implements Parcelable, Cloneable {
* <p>The Intent will have the following extra value:</p>
* <ul>
* <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> -
- * the phone number originally intended to be dialed.</li>
+ * the phone number dialed.</li>
* </ul>
* <p class="note">Starting in Android 15, this broadcast is no longer sent as an ordered
* broadcast. The <code>resultData</code> no longer has any effect and will not determine the
@@ -3800,6 +3800,14 @@ public class Intent implements Parcelable, Cloneable {
* {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
* permission to receive this Intent.</p>
*
+ * <p class="note">Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this broadcast is
+ * no longer sent as an ordered broadcast, and does not allow activity launches. This means
+ * that receivers may no longer change the phone number for the outgoing call, or cancel the
+ * outgoing call. This functionality is only possible using the
+ * {@link android.telecom.CallRedirectionService} API. Although background receivers are
+ * woken up to handle this intent, no guarantee is made as to the timeliness of the broadcast.
+ * </p>
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 7b181176ae25..0a264e34b94a 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
@@ -143,6 +144,7 @@ import java.util.List;
* @attr ref android.R.styleable#ColorStateListItem_color
* @attr ref android.R.styleable#ColorStateListItem_lStar
*/
+@RavenwoodKeepWholeClass
public class ColorStateList extends ComplexColor implements Parcelable {
private static final String TAG = "ColorStateList";
diff --git a/core/java/android/content/res/ComplexColor.java b/core/java/android/content/res/ComplexColor.java
index 58c6fc5174d3..a385ee397f3b 100644
--- a/core/java/android/content/res/ComplexColor.java
+++ b/core/java/android/content/res/ComplexColor.java
@@ -18,13 +18,14 @@ package android.content.res;
import android.annotation.ColorInt;
import android.content.res.Resources.Theme;
-import android.graphics.Color;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* Defines an abstract class for the complex color information, like
* {@link android.content.res.ColorStateList} or {@link android.content.res.GradientColor}
* @hide
*/
+@RavenwoodKeepWholeClass
public abstract class ComplexColor {
private int mChangingConfigurations;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index bf4d97d602d8..05596318aef5 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1124,7 +1124,6 @@ public class Resources {
*/
@NonNull
@Deprecated
- @RavenwoodThrow(blockedBy = ColorStateList.class)
public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException {
final ColorStateList csl = getColorStateList(id, null);
if (csl != null && csl.canApplyTheme()) {
@@ -1155,7 +1154,6 @@ public class Resources {
* color or multiple colors that can be selected based on a state.
*/
@NonNull
- @RavenwoodThrow(blockedBy = ColorStateList.class)
public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
@@ -1169,7 +1167,6 @@ public class Resources {
}
@NonNull
- @RavenwoodThrow(blockedBy = ColorStateList.class)
ColorStateList loadColorStateList(@NonNull TypedValue value, int id, @Nullable Theme theme)
throws NotFoundException {
return mResourcesImpl.loadColorStateList(this, value, id, theme);
@@ -1179,7 +1176,6 @@ public class Resources {
* @hide
*/
@NonNull
- @RavenwoodThrow(blockedBy = ComplexColor.class)
public ComplexColor loadComplexColor(@NonNull TypedValue value, int id, @Nullable Theme theme) {
return mResourcesImpl.loadComplexColor(this, value, id, theme);
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 90420dec64d1..e6b93427f413 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1126,7 +1126,6 @@ public class ResourcesImpl {
}
@Nullable
- @RavenwoodThrow(blockedBy = ComplexColor.class)
ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
Resources.Theme theme) {
if (TRACE_FOR_PRELOAD) {
@@ -1168,7 +1167,6 @@ public class ResourcesImpl {
}
@NonNull
- @RavenwoodThrow(blockedBy = ColorStateList.class)
ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
Resources.Theme theme)
throws NotFoundException {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index f8eeaa93872a..79185a10e156 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -28,7 +28,6 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.StrictMode;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
import android.ravenwood.annotation.RavenwoodThrow;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -598,7 +597,6 @@ public class TypedArray implements AutoCloseable {
* not an integer color or color state list.
*/
@Nullable
- @RavenwoodThrow(blockedBy = ColorStateList.class)
public ColorStateList getColorStateList(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 48d27855db74..a60c48e0aa8c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -80,7 +80,6 @@ import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -355,14 +354,7 @@ public class CameraDeviceImpl extends CameraDevice
mCameraId = cameraId;
if (Flags.singleThreadExecutor()) {
mDeviceCallback = new ClientStateCallback(executor, callback);
- mDeviceExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread thread = Executors.defaultThreadFactory().newThread(r);
- thread.setName("CameraDeviceExecutor");
- return thread;
- }
- });
+ mDeviceExecutor = Executors.newSingleThreadExecutor();
} else {
mDeviceCallback = callback;
mDeviceExecutor = executor;
diff --git a/core/java/android/hardware/radio/flags.aconfig b/core/java/android/hardware/radio/flags.aconfig
index c9ab62ddc27f..c99dc049aac9 100644
--- a/core/java/android/hardware/radio/flags.aconfig
+++ b/core/java/android/hardware/radio/flags.aconfig
@@ -8,3 +8,11 @@ flag {
description: "Feature flag for improved HD radio support with less vendor extensions"
bug: "280300929"
}
+
+flag {
+ name: "hd_radio_emergency_alert_system"
+ is_exported: true
+ namespace: "car_framework"
+ description: "Feature flag for HD radio emergency alert system support"
+ bug: "361348719"
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index c7751e3e5cea..c4d12d4336c6 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1997,6 +1997,8 @@ public abstract class BatteryStats {
STATE2_VIDEO_ON_FLAG | STATE2_FLASHLIGHT_FLAG | STATE2_CAMERA_FLAG
| STATE2_GPS_SIGNAL_QUALITY_MASK;
+ public static final int GNSS_SIGNAL_QUALITY_NONE = 2;
+
@UnsupportedAppUsage
public int states2;
@@ -2220,7 +2222,7 @@ public abstract class BatteryStats {
modemRailChargeMah = 0;
wifiRailChargeMah = 0;
states = 0;
- states2 = 0;
+ states2 = GNSS_SIGNAL_QUALITY_NONE << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
wakelockTag = null;
wakeReasonTag = null;
eventCode = EVENT_NONE;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index b7556dfb51af..4bc3dbedeb94 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -708,9 +708,16 @@ public class Binder implements IBinder {
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
public final native void markVintfStability();
+ /** @hide */
+ private void markVintfStability$ravenwood() {
+ // This is not useful for Ravenwood which uses local binder.
+ // TODO(b/361785059): Use real native libbinder.
+ }
+
/**
* Use a VINTF-stability binder w/o VINTF requirements. Should be called
* on a binder before it is sent out of process.
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 4cc057a8e0ed..e80efd2a9380 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -41,7 +41,6 @@ import android.content.ContentResolver;
import android.net.Uri;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
import android.ravenwood.annotation.RavenwoodReplace;
import android.ravenwood.annotation.RavenwoodThrow;
import android.system.ErrnoException;
@@ -77,8 +76,6 @@ import java.nio.ByteOrder;
* you to close it when done with it.
*/
@RavenwoodKeepWholeClass
-@RavenwoodNativeSubstitutionClass(
- "com.android.platform.test.ravenwood.nativesubstitution.ParcelFileDescriptor_host")
public class ParcelFileDescriptor implements Parcelable, Closeable {
private static final String TAG = "ParcelFileDescriptor";
@@ -206,11 +203,11 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
}
mWrapped = null;
mFd = fd;
- setFdOwner(mFd);
+ IoUtils.setFdOwner(mFd, this);
mCommFd = commChannel;
if (mCommFd != null) {
- setFdOwner(mCommFd);
+ IoUtils.setFdOwner(mCommFd, this);
}
mGuard.open("close");
@@ -298,7 +295,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd,
@NonNull Handler handler, @NonNull OnCloseListener listener) throws IOException {
final FileDescriptor original = new FileDescriptor();
- setFdInt(original, pfd.detachFd());
+ original.setInt$(pfd.detachFd());
return fromFd(original, handler, listener);
}
@@ -363,18 +360,10 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
}
}
- @RavenwoodReplace
private static void closeInternal(FileDescriptor fd) {
IoUtils.closeQuietly(fd);
}
- private static void closeInternal$ravenwood(FileDescriptor fd) {
- try {
- Os.close(fd);
- } catch (ErrnoException ignored) {
- }
- }
-
/**
* Create a new ParcelFileDescriptor that is a dup of an existing
* FileDescriptor. This obeys standard POSIX semantics, where the
@@ -385,7 +374,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
try {
final FileDescriptor fd = new FileDescriptor();
int intfd = Os.fcntlInt(orig, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0);
- setFdInt(fd, intfd);
+ fd.setInt$(intfd);
return new ParcelFileDescriptor(fd);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
@@ -418,12 +407,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
*/
public static ParcelFileDescriptor fromFd(int fd) throws IOException {
final FileDescriptor original = new FileDescriptor();
- setFdInt(original, fd);
+ original.setInt$(fd);
try {
final FileDescriptor dup = new FileDescriptor();
int intfd = Os.fcntlInt(original, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0);
- setFdInt(dup, intfd);
+ dup.setInt$(intfd);
return new ParcelFileDescriptor(dup);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
@@ -446,7 +435,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
*/
public static ParcelFileDescriptor adoptFd(int fd) {
final FileDescriptor fdesc = new FileDescriptor();
- setFdInt(fdesc, fd);
+ fdesc.setInt$(fd);
return new ParcelFileDescriptor(fdesc);
}
@@ -703,7 +692,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
@RavenwoodThrow(reason = "Os.readlink() and Os.stat()")
public static File getFile(FileDescriptor fd) throws IOException {
try {
- final String path = Os.readlink("/proc/self/fd/" + getFdInt(fd));
+ final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
if (OsConstants.S_ISREG(Os.stat(path).st_mode)
|| OsConstants.S_ISCHR(Os.stat(path).st_mode)) {
return new File(path);
@@ -783,7 +772,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
if (mClosed) {
throw new IllegalStateException("Already closed");
}
- return getFdInt(mFd);
+ return mFd.getInt$();
}
}
@@ -805,7 +794,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
if (mClosed) {
throw new IllegalStateException("Already closed");
}
- int fd = acquireRawFd(mFd);
+ int fd = IoUtils.acquireRawFd(mFd);
writeCommStatusAndClose(Status.DETACHED, null);
mClosed = true;
mGuard.close();
@@ -1265,38 +1254,6 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
}
}
- private static native void setFdInt$ravenwood(FileDescriptor fd, int fdInt);
- private static native int getFdInt$ravenwood(FileDescriptor fd);
-
- @RavenwoodReplace
- private static void setFdInt(FileDescriptor fd, int fdInt) {
- fd.setInt$(fdInt);
- }
-
- @RavenwoodReplace
- private static int getFdInt(FileDescriptor fd) {
- return fd.getInt$();
- }
-
- @RavenwoodReplace
- private void setFdOwner(FileDescriptor fd) {
- IoUtils.setFdOwner(fd, this);
- }
-
- private void setFdOwner$ravenwood(FileDescriptor fd) {
- // FD owners currently unsupported under Ravenwood; ignored
- }
-
- @RavenwoodReplace
- private int acquireRawFd(FileDescriptor fd) {
- return IoUtils.acquireRawFd(fd);
- }
-
- private int acquireRawFd$ravenwood(FileDescriptor fd) {
- // FD owners currently unsupported under Ravenwood; return FD directly
- return getFdInt(fd);
- }
-
@RavenwoodReplace
private static boolean isAtLeastQ() {
return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index db06a6ba0ef5..3b2041b0d50a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -838,16 +838,11 @@ public class Process {
/**
* Returns true if the current process is a 64-bit runtime.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean is64Bit() {
return VMRuntime.getRuntime().is64Bit();
}
- /** @hide */
- public static final boolean is64Bit$ravenwood() {
- return "amd64".equals(System.getProperty("os.arch"));
- }
-
private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
/** @hide */
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 17d2790eac96..013ec5f35761 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -28,7 +28,9 @@ import android.os.RemoteException;
import android.util.Log;
import android.view.WindowManager;
+import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
@@ -52,43 +54,51 @@ public abstract class DreamOverlayService extends Service {
// An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
// requests to the {@link DreamOverlayService}
private static class OverlayClient extends IDreamOverlayClient.Stub {
- private final DreamOverlayService mService;
+ private final WeakReference<DreamOverlayService> mService;
private boolean mShowComplications;
private ComponentName mDreamComponent;
IDreamOverlayCallback mDreamOverlayCallback;
- OverlayClient(DreamOverlayService service) {
+ OverlayClient(WeakReference<DreamOverlayService> service) {
mService = service;
}
+ private void applyToDream(Consumer<DreamOverlayService> consumer) {
+ final DreamOverlayService service = mService.get();
+
+ if (service != null) {
+ consumer.accept(service);
+ }
+ }
+
@Override
public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
String dreamComponent, boolean shouldShowComplications) throws RemoteException {
mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
mShowComplications = shouldShowComplications;
mDreamOverlayCallback = callback;
- mService.startDream(this, params);
+ applyToDream(dreamOverlayService -> dreamOverlayService.startDream(this, params));
}
@Override
public void wakeUp() {
- mService.wakeUp(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.wakeUp(this));
}
@Override
public void endDream() {
- mService.endDream(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.endDream(this));
}
@Override
public void comeToFront() {
- mService.comeToFront(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.comeToFront(this));
}
@Override
public void onWakeRequested() {
if (Flags.dreamWakeRedirect()) {
- mService.onWakeRequested();
+ applyToDream(DreamOverlayService::onWakeRequested);
}
}
@@ -161,17 +171,24 @@ public abstract class DreamOverlayService extends Service {
});
}
- private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+ private static class DreamOverlay extends IDreamOverlay.Stub {
+ private final WeakReference<DreamOverlayService> mService;
+
+ DreamOverlay(DreamOverlayService service) {
+ mService = new WeakReference<>(service);
+ }
+
@Override
public void getClient(IDreamOverlayClientCallback callback) {
try {
- callback.onDreamOverlayClient(
- new OverlayClient(DreamOverlayService.this));
+ callback.onDreamOverlayClient(new OverlayClient(mService));
} catch (RemoteException e) {
Log.e(TAG, "could not send client to callback", e);
}
}
- };
+ }
+
+ private final IDreamOverlay mDreamOverlay = new DreamOverlay(this);
public DreamOverlayService() {
}
@@ -195,6 +212,12 @@ public abstract class DreamOverlayService extends Service {
}
}
+ @Override
+ public void onDestroy() {
+ mCurrentClient = null;
+ super.onDestroy();
+ }
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 0242de0b972c..c3585e3c5288 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -81,6 +81,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.function.Consumer;
/**
@@ -1261,7 +1262,7 @@ public class DreamService extends Service implements Window.Callback {
@Override
public final IBinder onBind(Intent intent) {
if (mDebug) Slog.v(mTag, "onBind() intent = " + intent);
- mDreamServiceWrapper = new DreamServiceWrapper();
+ mDreamServiceWrapper = new DreamServiceWrapper(new WeakReference<>(this));
final ComponentName overlayComponent = intent.getParcelableExtra(
EXTRA_DREAM_OVERLAY_COMPONENT, ComponentName.class);
@@ -1631,7 +1632,8 @@ public class DreamService extends Service implements Window.Callback {
i.setComponent(mInjector.getDreamActivityComponent());
i.setPackage(mInjector.getDreamPackageName());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
- DreamActivity.setCallback(i, new DreamActivityCallbacks(mDreamToken));
+ DreamActivity.setCallback(i,
+ new DreamActivityCallbacks(mDreamToken, new WeakReference<>(this)));
final ServiceInfo serviceInfo = mInjector.getServiceInfo();
final CharSequence title = fetchDreamLabel(mInjector.getPackageManager(),
mInjector.getResources(), serviceInfo, isPreviewMode);
@@ -1845,22 +1847,37 @@ public class DreamService extends Service implements Window.Callback {
* uses it to control the DreamService. It is also used to receive callbacks from the
* DreamActivity.
*/
- final class DreamServiceWrapper extends IDreamService.Stub {
+ static final class DreamServiceWrapper extends IDreamService.Stub {
+ final WeakReference<DreamService> mService;
+
+ DreamServiceWrapper(WeakReference<DreamService> service) {
+ mService = service;
+ }
+
+ private void post(Consumer<DreamService> consumer) {
+ final DreamService service = mService.get();
+
+ if (service == null) {
+ return;
+ }
+
+ service.mHandler.post(() -> consumer.accept(service));
+ }
+
@Override
public void attach(final IBinder dreamToken, final boolean canDoze,
final boolean isPreviewMode, IRemoteCallback started) {
- mHandler.post(
- () -> DreamService.this.attach(dreamToken, canDoze, isPreviewMode, started));
+ post(dreamService -> dreamService.attach(dreamToken, canDoze, isPreviewMode, started));
}
@Override
public void detach() {
- mHandler.post(DreamService.this::detach);
+ post(DreamService::detach);
}
@Override
public void wakeUp() {
- mHandler.post(() -> DreamService.this.wakeUp(true /*fromSystem*/));
+ post(dreamService -> dreamService.wakeUp(true /*fromSystem*/));
}
@Override
@@ -1868,48 +1885,70 @@ public class DreamService extends Service implements Window.Callback {
if (!dreamHandlesBeingObscured()) {
return;
}
+ post(DreamService::comeToFront);
+ }
+ }
- mHandler.post(DreamService.this::comeToFront);
+ private void onActivityCreated(DreamActivity activity, IBinder dreamToken) {
+ if (dreamToken != mDreamToken || mFinished) {
+ Slog.d(TAG, "DreamActivity was created after the dream was finished or "
+ + "a new dream started, finishing DreamActivity");
+ if (!activity.isFinishing()) {
+ activity.finishAndRemoveTask();
+ }
+ return;
+ }
+ if (mActivity != null) {
+ Slog.w(TAG, "A DreamActivity has already been started, "
+ + "finishing latest DreamActivity");
+ if (!activity.isFinishing()) {
+ activity.finishAndRemoveTask();
+ }
+ return;
}
+
+ mActivity = activity;
+ onWindowCreated(activity.getWindow());
+ }
+
+ private void onActivityDestroyed() {
+ mActivity = null;
+ mWindow = null;
+ detach();
}
/** @hide */
@VisibleForTesting
- public final class DreamActivityCallbacks extends Binder {
+ public static final class DreamActivityCallbacks extends Binder {
private final IBinder mActivityDreamToken;
+ private WeakReference<DreamService> mService;
- DreamActivityCallbacks(IBinder token) {
+ DreamActivityCallbacks(IBinder token, WeakReference<DreamService> service) {
mActivityDreamToken = token;
+ mService = service;
}
/** Callback when the {@link DreamActivity} has been created */
public void onActivityCreated(DreamActivity activity) {
- if (mActivityDreamToken != mDreamToken || mFinished) {
- Slog.d(TAG, "DreamActivity was created after the dream was finished or "
- + "a new dream started, finishing DreamActivity");
- if (!activity.isFinishing()) {
- activity.finishAndRemoveTask();
- }
- return;
- }
- if (mActivity != null) {
- Slog.w(TAG, "A DreamActivity has already been started, "
- + "finishing latest DreamActivity");
- if (!activity.isFinishing()) {
- activity.finishAndRemoveTask();
- }
+ final DreamService service = mService.get();
+
+ if (service == null) {
return;
}
- mActivity = activity;
- onWindowCreated(activity.getWindow());
+ service.onActivityCreated(activity, mActivityDreamToken);
}
/** Callback when the {@link DreamActivity} has been destroyed */
public void onActivityDestroyed() {
- mActivity = null;
- mWindow = null;
- detach();
+ final DreamService service = mService.get();
+
+ if (service == null) {
+ return;
+ }
+
+ service.onActivityDestroyed();
+ mService = null;
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 57acc71c20ea..918e591069fb 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -241,10 +241,11 @@ public class ZenModeConfig implements Parcelable {
// ZenModeConfig XML versions distinguishing key changes.
public static final int XML_VERSION_ZEN_UPGRADE = 8;
public static final int XML_VERSION_MODES_API = 11;
+ public static final int XML_VERSION_MODES_UI = 12;
- // TODO: b/310620812 - Update XML_VERSION and update default_zen_config.xml accordingly when
- // modes_api is inlined.
- private static final int XML_VERSION = 10;
+ // TODO: b/310620812, b/344831624 - Update XML_VERSION and update default_zen_config.xml
+ // accordingly when modes_api / modes_ui are inlined.
+ private static final int XML_VERSION_PRE_MODES = 10;
public static final String ZEN_TAG = "zen";
private static final String ZEN_ATT_VERSION = "version";
private static final String ZEN_ATT_USER = "user";
@@ -952,7 +953,13 @@ public class ZenModeConfig implements Parcelable {
}
public static int getCurrentXmlVersion() {
- return Flags.modesApi() ? XML_VERSION_MODES_API : XML_VERSION;
+ if (Flags.modesUi()) {
+ return XML_VERSION_MODES_UI;
+ } else if (Flags.modesApi()) {
+ return XML_VERSION_MODES_API;
+ } else {
+ return XML_VERSION_PRE_MODES;
+ }
}
public static ZenModeConfig readXml(TypedXmlPullParser parser)
@@ -2607,7 +2614,7 @@ public class ZenModeConfig implements Parcelable {
@AutomaticZenRule.Type
public int type = AutomaticZenRule.TYPE_UNKNOWN;
public String triggerDescription;
- public String iconResName;
+ @Nullable public String iconResName;
public boolean allowManualInvocation;
@AutomaticZenRule.ModifiableField public int userModifiedFields;
@ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields;
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 40070c7557bc..d33c95e06677 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -297,3 +297,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "typeface_redesign"
+ namespace: "text"
+ description: "Decouple variation settings, weight and style information from Typeface class"
+ bug: "361260253"
+}
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index be1ec4187ddc..9845f9e7b804 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -18,7 +18,6 @@ package android.util;
import android.compat.annotation.UnsupportedAppUsage;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -226,16 +225,10 @@ public class LruCache<K, V> {
}
}
- @android.ravenwood.annotation.RavenwoodReplace
private Map.Entry<K, V> eldest() {
return map.eldest();
}
- private Map.Entry<K, V> eldest$ravenwood() {
- final Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();
- return it.hasNext() ? it.next() : null;
- }
-
/**
* Removes the entry for {@code key} if it exists.
*
diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java
index 17adb32fb846..25f321ebb6b5 100644
--- a/core/java/android/util/StateSet.java
+++ b/core/java/android/util/StateSet.java
@@ -16,6 +16,8 @@
package android.util;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+
import com.android.internal.R;
/**
@@ -34,6 +36,7 @@ import com.android.internal.R;
* and not have static methods here but there is some concern about
* performance since these methods are called during view drawing.
*/
+@RavenwoodKeepWholeClass
public class StateSet {
/**
* The order here is very important to
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 15b0c13de524..1f7ed8be357c 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2344,6 +2344,8 @@ public final class Display {
* SurfaceControl.DisplayMode
* @hide
*/
+ @SuppressWarnings("UnflaggedApi") // For testing only
+ @TestApi
public boolean isSynthetic() {
return mIsSynthetic;
}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 8912035c0be3..ab9bd1fdfd72 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -730,13 +730,13 @@ public class HandwritingInitiator {
/* The distance between point (x, y) and rect, there are 2 basic cases:
* a) The distance is the distance from (x, y) to the closest corner on rect.
- * o | |
+ * o | |
* ---+-----+---
* | |
* ---+-----+---
* | |
* b) The distance is the distance from (x, y) to the closest edge on rect.
- * | o |
+ * | o |
* ---+-----+---
* | |
* ---+-----+---
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 6568912a82c0..91e9230cdc6a 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -35,8 +35,8 @@ import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN
import static android.view.InsetsController.LayoutInsetsDuringAnimation;
import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsSource.SIDE_BOTTOM;
-import static android.view.InsetsSource.SIDE_NONE;
import static android.view.InsetsSource.SIDE_LEFT;
+import static android.view.InsetsSource.SIDE_NONE;
import static android.view.InsetsSource.SIDE_RIGHT;
import static android.view.InsetsSource.SIDE_TOP;
import static android.view.WindowInsets.Type.ime;
@@ -100,6 +100,8 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
private @InsetsType int mControllingTypes;
private final InsetsAnimationControlCallbacks mController;
private final WindowInsetsAnimation mAnimation;
+ private final long mDurationMs;
+ private final Interpolator mInterpolator;
/** @see WindowInsetsAnimationController#hasZeroInsetsIme */
private final boolean mHasZeroInsetsIme;
private final CompatibilityInfo.Translator mTranslator;
@@ -120,8 +122,8 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
- @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
- Interpolator interpolator, @AnimationType int animationType,
+ @InsetsType int types, InsetsAnimationControlCallbacks controller,
+ InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
mControls = controls;
@@ -155,8 +157,10 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
}
mPendingInsets = mCurrentInsets;
- mAnimation = new WindowInsetsAnimation(mTypes, interpolator,
- durationMs);
+ mDurationMs = insetsAnimationSpec.getDurationMs(mHasZeroInsetsIme);
+ mInterpolator = insetsAnimationSpec.getInsetsInterpolator(mHasZeroInsetsIme);
+
+ mAnimation = new WindowInsetsAnimation(mTypes, mInterpolator, mDurationMs);
mAnimation.setAlpha(getCurrentAlpha());
mAnimationType = animationType;
mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
@@ -186,6 +190,16 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
}
@Override
+ public long getDurationMs() {
+ return mDurationMs;
+ }
+
+ @Override
+ public Interpolator getInsetsInterpolator() {
+ return mInterpolator;
+ }
+
+ @Override
public void setReadyDispatched(boolean dispatched) {
mReadyDispatched = dispatched;
}
diff --git a/core/java/android/view/InsetsAnimationSpec.java b/core/java/android/view/InsetsAnimationSpec.java
new file mode 100644
index 000000000000..7ad6661abffd
--- /dev/null
+++ b/core/java/android/view/InsetsAnimationSpec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.animation.Interpolator;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Used by {@link InsetsAnimationControlImpl}
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public interface InsetsAnimationSpec {
+ /**
+ * @param hasZeroInsetsIme whether IME has no insets (floating, fullscreen or non-overlapping).
+ * @return Duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+ */
+ long getDurationMs(boolean hasZeroInsetsIme);
+ /**
+ * @param hasZeroInsetsIme whether IME has no insets (floating, fullscreen or non-overlapping).
+ * @return The interpolator used for the animation
+ */
+ Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme);
+}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 1b3b3ebbecfc..fc185bc73735 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -33,7 +33,6 @@ import android.view.InsetsController.LayoutInsetsDuringAnimation;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
-import android.view.animation.Interpolator;
import android.view.inputmethod.ImeTracker;
/**
@@ -110,15 +109,15 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
@UiThread
public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
- @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
- Interpolator interpolator, @AnimationType int animationType,
+ @InsetsType int types, InsetsAnimationControlCallbacks controller,
+ InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CompatibilityInfo.Translator translator, Handler mainThreadHandler,
@Nullable ImeTracker.Token statsToken) {
mMainThreadHandler = mainThreadHandler;
mOuterCallbacks = controller;
mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
- mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
+ mCallbacks, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
translator, statsToken);
InsetsAnimationThread.getHandler().post(() -> {
if (mControl.isCancelled()) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7896cbde678a..b1df51f7affa 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -366,7 +366,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
* animate insets.
*/
public static class InternalAnimationControlListener
- implements WindowInsetsAnimationControlListener {
+ implements WindowInsetsAnimationControlListener, InsetsAnimationSpec {
private WindowInsetsAnimationController mController;
private ValueAnimator mAnimator;
@@ -374,7 +374,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private final boolean mHasAnimationCallbacks;
private final @InsetsType int mRequestedTypes;
private final @Behavior int mBehavior;
- private final long mDurationMs;
private final boolean mDisable;
private final int mFloatingImeBottomInset;
private final WindowInsetsAnimationControlListener mLoggingListener;
@@ -388,7 +387,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
mBehavior = behavior;
- mDurationMs = calculateDurationMs();
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
mLoggingListener = loggingListener;
@@ -407,13 +405,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
onAnimationFinish();
return;
}
+ final boolean hasZeroInsetsIme = controller.hasZeroInsetsIme();
mAnimator = ValueAnimator.ofFloat(0f, 1f);
- mAnimator.setDuration(mDurationMs);
+ mAnimator.setDuration(controller.getDurationMs());
mAnimator.setInterpolator(new LinearInterpolator());
Insets hiddenInsets = controller.getHiddenStateInsets();
// IME with zero insets is a special case: it will animate-in from offscreen and end
// with final insets of zero and vice-versa.
- hiddenInsets = controller.hasZeroInsetsIme()
+ hiddenInsets = hasZeroInsetsIme
? Insets.of(hiddenInsets.left, hiddenInsets.top, hiddenInsets.right,
mFloatingImeBottomInset)
: hiddenInsets;
@@ -423,7 +422,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
Insets end = mShow
? controller.getShownStateInsets()
: hiddenInsets;
- Interpolator insetsInterpolator = getInsetsInterpolator();
+ Interpolator insetsInterpolator = controller.getInsetsInterpolator();
Interpolator alphaInterpolator = getAlphaInterpolator();
mAnimator.addUpdateListener(animation -> {
float rawFraction = animation.getAnimatedFraction();
@@ -486,9 +485,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
}
- protected Interpolator getInsetsInterpolator() {
+ @Override
+ public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks) {
+ if (mHasAnimationCallbacks && hasZeroInsetsIme) {
return SYNC_IME_INTERPOLATOR;
} else if (mShow) {
return LINEAR_OUT_SLOW_IN_INTERPOLATOR;
@@ -507,10 +507,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
Interpolator getAlphaInterpolator() {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks) {
+ if (mHasAnimationCallbacks && !mController.hasZeroInsetsIme()) {
return input -> 1f;
} else if (mShow) {
-
// Alpha animation takes half the time with linear interpolation;
return input -> Math.min(1f, 2 * input);
} else {
@@ -534,16 +533,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (DEBUG) Log.d(TAG, "onAnimationFinish showOnFinish: " + mShow);
}
- /**
- * To get the animation duration in MS.
- */
- public long getDurationMs() {
- return mDurationMs;
- }
-
- private long calculateDurationMs() {
+ @Override
+ public long getDurationMs(boolean hasZeroInsetsIme) {
if ((mRequestedTypes & ime()) != 0) {
- if (mHasAnimationCallbacks) {
+ if (mHasAnimationCallbacks && hasZeroInsetsIme) {
return ANIMATION_DURATION_SYNC_IME_MS;
} else {
return ANIMATION_DURATION_UNSYNC_IME_MS;
@@ -593,13 +586,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private static class PendingControlRequest {
PendingControlRequest(@InsetsType int types, WindowInsetsAnimationControlListener listener,
- long durationMs, Interpolator interpolator, @AnimationType int animationType,
+ InsetsAnimationSpec insetsAnimationSpec,
+ @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CancellationSignal cancellationSignal, boolean useInsetsAnimationThread) {
this.types = types;
this.listener = listener;
- this.durationMs = durationMs;
- this.interpolator = interpolator;
+ this.mInsetsAnimationSpec = insetsAnimationSpec;
this.animationType = animationType;
this.layoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
this.cancellationSignal = cancellationSignal;
@@ -608,8 +601,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@InsetsType int types;
final WindowInsetsAnimationControlListener listener;
- final long durationMs;
- final Interpolator interpolator;
+ final InsetsAnimationSpec mInsetsAnimationSpec;
final @AnimationType int animationType;
final @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation;
final CancellationSignal cancellationSignal;
@@ -1201,12 +1193,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// We are about to playing the default animation. Passing a null frame indicates the
// controlled types should be animated regardless of the frame.
- controlAnimationUnchecked(
- pendingRequest.types, pendingRequest.cancellationSignal,
- pendingRequest.listener, null /* frame */,
- true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
- pendingRequest.animationType,
- pendingRequest.layoutInsetsDuringAnimation,
+ controlAnimationUnchecked(pendingRequest.types, pendingRequest.cancellationSignal,
+ pendingRequest.listener, null /* frame */, true /* fromIme */,
+ pendingRequest.mInsetsAnimationSpec,
+ pendingRequest.animationType, pendingRequest.layoutInsetsDuringAnimation,
pendingRequest.useInsetsAnimationThread, statsToken);
}
@@ -1327,18 +1317,26 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
mHost.getInputMethodManager(), null /* icProto */);
}
+ InsetsAnimationSpec spec = new InsetsAnimationSpec() {
+ @Override
+ public long getDurationMs(boolean hasZeroInsetsIme) {
+ return durationMs;
+ }
+ @Override
+ public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
+ return interpolator;
+ }
+ };
// TODO(b/342111149): Create statsToken here once ImeTracker#onStart becomes async.
- controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
- interpolator, animationType,
- getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
+ controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, spec,
+ animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
false /* useInsetsAnimationThread */, null);
}
private void controlAnimationUnchecked(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
- long durationMs, Interpolator interpolator,
- @AnimationType int animationType,
+ InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
@@ -1349,7 +1347,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// However, we might reject the request in some cases, such as delaying showing IME or
// rejecting showing IME.
controlAnimationUncheckedInner(types, cancellationSignal, listener, frame, fromIme,
- durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
+ insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
useInsetsAnimationThread, statsToken);
// We are finishing setting the requested visible types. Report them to the server
@@ -1360,8 +1358,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private void controlAnimationUncheckedInner(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
- long durationMs, Interpolator interpolator,
- @AnimationType int animationType,
+ InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
if ((types & mTypesBeingCancelled) != 0) {
@@ -1418,8 +1415,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// TODO (b/323319146) remove layoutInsetsDuringAnimation from
// PendingControlRequest, as it is now only used for showing
final PendingControlRequest request = new PendingControlRequest(types,
- listener, durationMs,
- interpolator, animationType, LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+ listener, insetsAnimationSpec, animationType,
+ LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
cancellationSignal, false /* useInsetsAnimationThread */);
mPendingImeControlRequest = request;
// only add a timeout when the control is not currently showing
@@ -1460,11 +1457,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (!imeReady) {
// IME isn't ready, all requested types will be animated once IME is ready
abortPendingImeControlRequest();
- final PendingControlRequest request = new PendingControlRequest(types,
- listener, durationMs,
- interpolator, animationType, layoutInsetsDuringAnimation,
- cancellationSignal,
- useInsetsAnimationThread);
+ final PendingControlRequest request = new PendingControlRequest(types, listener,
+ insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+ cancellationSignal, useInsetsAnimationThread);
mPendingImeControlRequest = request;
mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS);
if (DEBUG) Log.d(TAG, "Ime not ready. Create pending request");
@@ -1520,11 +1515,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
final InsetsAnimationControlRunner runner = useInsetsAnimationThread
? new InsetsAnimationThreadControlRunner(controls,
- frame, mState, listener, typesReady, this, durationMs, interpolator,
- animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
- mHost.getHandler(), statsToken)
+ frame, mState, listener, typesReady, this,
+ insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+ mHost.getTranslator(), mHost.getHandler(), statsToken)
: new InsetsAnimationControlImpl(controls,
- frame, mState, listener, typesReady, this, durationMs, interpolator,
+ frame, mState, listener, typesReady, this, insetsAnimationSpec,
animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
statsToken);
if ((typesReady & WindowInsets.Type.ime()) != 0) {
@@ -2023,7 +2018,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// the controlled types should be animated regardless of the frame.
controlAnimationUnchecked(
types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
- listener.getDurationMs(), listener.getInsetsInterpolator(),
+ listener /* insetsAnimationSpec */,
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
!hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index 6e6222187e49..f90b8411e333 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -233,6 +233,16 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner
}
@Override
+ public long getDurationMs() {
+ return 0;
+ }
+
+ @Override
+ public Interpolator getInsetsInterpolator() {
+ return null;
+ }
+
+ @Override
public void setReadyDispatched(boolean dispatched) {
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 72d2d3b538b3..326e34b258e5 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -128,7 +128,16 @@ import java.util.Objects;
* ev.getPointerId(p), ev.getX(p), ev.getY(p));
* }
* }
- * </code></pre></p>
+ * </code></pre></p><p>
+ * Developers should keep in mind that it is especially important to consume all samples
+ * in a batched event when processing relative values that report changes since the last
+ * event or sample. Examples of such relative axes include {@link #AXIS_RELATIVE_X},
+ * {@link #AXIS_RELATIVE_Y}, and many of the axes prefixed with {@code AXIS_GESTURE_}.
+ * In these cases, developers should first consume all historical values using
+ * {@link #getHistoricalAxisValue(int, int)} and then consume the current values using
+ * {@link #getAxisValue(int)} like in the example above, as these relative values are
+ * not accumulated in a batched event.
+ * </p>
*
* <h3>Device Types</h3>
* <p>
@@ -1117,6 +1126,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* the location but this axis reports the difference which allows the app to see
* how the mouse is moved.
* </ul>
+ * </p><p>
+ * These values are relative to the state from the last sample, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical samples.
* </p>
*
* @see #getAxisValue(int, int)
@@ -1130,6 +1142,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* Axis constant: The movement of y position of a motion event.
* <p>
* This is similar to {@link #AXIS_RELATIVE_X} but for y-axis.
+ * </p><p>
+ * These values are relative to the state from the last sample, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical samples.
* </p>
*
* @see #getAxisValue(int, int)
@@ -1324,8 +1339,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of
* -0.1.
* </ul>
- * These values are relative to the state from the last event, not accumulated, so developers
- * should make sure to process this axis value for all batched historical events.
+ * These values are relative to the state from the last sample, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical samples.
* <p>
* This axis is only set on the first pointer in a motion event.
*/
@@ -1345,8 +1360,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* <li>For a touch pad, reports the distance that should be scrolled in the X axis as a result
* of the user's two-finger scroll gesture, in display pixels.
* </ul>
- * These values are relative to the state from the last event, not accumulated, so developers
- * should make sure to process this axis value for all batched historical events.
+ * These values are relative to the state from the last sample, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical samples.
* <p>
* This axis is only set on the first pointer in a motion event.
*/
@@ -1367,8 +1382,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* making a pinch gesture, as a proportion of the previous distance. For example, if the fingers
* were 50 units apart and are now 52 units apart, the scale factor would be 1.04.
* </ul>
- * These values are relative to the state from the last event, not accumulated, so developers
- * should make sure to process this axis value for all batched historical events.
+ * These values are relative to the state from the last sample, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical samples.
* <p>
* This axis is only set on the first pointer in a motion event.
*/
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 6578e9b6c20c..d3ea9829c680 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -23,6 +23,7 @@ import android.annotation.SuppressLint;
import android.graphics.Insets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
+import android.view.animation.Interpolator;
/**
* Controller for app-driven animation of system windows.
@@ -188,4 +189,16 @@ public interface WindowInsetsAnimationController {
* fullscreen or non-overlapping).
*/
boolean hasZeroInsetsIme();
+
+ /**
+ * @hide
+ * @return The duration of the animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+ */
+ long getDurationMs();
+
+ /**
+ * @hide
+ * @return The interpolator of the animation.
+ */
+ Interpolator getInsetsInterpolator();
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index a5ba294d6a19..fe6aafbd7e16 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -982,6 +982,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private long mParentNodeId = UNDEFINED_NODE_ID;
private long mLabelForId = UNDEFINED_NODE_ID;
private long mLabeledById = UNDEFINED_NODE_ID;
+ private LongArray mLabeledByIds;
private long mTraversalBefore = UNDEFINED_NODE_ID;
private long mTraversalAfter = UNDEFINED_NODE_ID;
@@ -3599,6 +3600,133 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Adds the view which serves as the label of the view represented by
+ * this info for accessibility purposes. When multiple labels are
+ * added, the content from each label is combined in the order that
+ * they are added.
+ * <p>
+ * If visible text can be used to describe or give meaning to this UI,
+ * this method is preferred. For example, a TextView before an EditText
+ * in the UI usually specifies what information is contained in the
+ * EditText. Hence, the EditText is labeled by the TextView.
+ * </p>
+ *
+ * @param label A view that labels this node's source.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public void addLabeledBy(@NonNull View label) {
+ addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Adds the view which serves as the label of the view represented by
+ * this info for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the label. When multiple
+ * labels are added, the content from each label is combined in the order
+ * that they are added.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * If visible text can be used to describe or give meaning to this UI,
+ * this method is preferred. For example, a TextView before an EditText
+ * in the UI usually specifies what information is contained in the
+ * EditText. Hence, the EditText is labeled by the TextView.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root A root whose virtual descendant labels this node's source.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public void addLabeledBy(@NonNull View root, int virtualDescendantId) {
+ enforceNotSealed();
+ Preconditions.checkNotNull(root, "%s must not be null", root);
+ if (mLabeledByIds == null) {
+ mLabeledByIds = new LongArray();
+ }
+ mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId);
+ mLabeledByIds.add(mLabeledById);
+ }
+
+ /**
+ * Gets the list of node infos which serve as the labels of the view represented by
+ * this info for accessibility purposes.
+ *
+ * @return The list of labels in the order that they were added.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public @NonNull List<AccessibilityNodeInfo> getLabeledByList() {
+ enforceSealed();
+ List<AccessibilityNodeInfo> labels = new ArrayList<>();
+ if (mLabeledByIds == null) {
+ return labels;
+ }
+ for (int i = 0; i < mLabeledByIds.size(); i++) {
+ labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i)));
+ }
+ return labels;
+ }
+
+ /**
+ * Removes a label. If the label was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param label The node which serves as this node's label.
+ * @return true if the label was present
+ * @see #addLabeledBy(View)
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public boolean removeLabeledBy(@NonNull View label) {
+ return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Removes a label which is a virtual descendant of the given
+ * <code>root</code>. If <code>virtualDescendantId</code> is
+ * {@link View#NO_ID} the root is set as the label. If the label
+ * was not previously added to the node, calling this method has
+ * no effect.
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual node which serves as this node's label.
+ * @return true if the label was present
+ * @see #addLabeledBy(View, int)
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+ public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final LongArray labeledByIds = mLabeledByIds;
+ if (labeledByIds == null) {
+ return false;
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ if (mLabeledById == labeledById) {
+ mLabeledById = UNDEFINED_NODE_ID;
+ }
+ final int index = labeledByIds.indexOf(labeledById);
+ if (index < 0) {
+ return false;
+ }
+ labeledByIds.remove(index);
+ return true;
+ }
+
+ /**
* Sets the view which serves as the label of the view represented by
* this info for accessibility purposes.
*
@@ -3631,7 +3759,17 @@ public class AccessibilityNodeInfo implements Parcelable {
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ if (Flags.supportMultipleLabeledby()) {
+ if (mLabeledByIds == null) {
+ mLabeledByIds = new LongArray();
+ } else {
+ mLabeledByIds.clear();
+ }
+ }
mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ if (Flags.supportMultipleLabeledby()) {
+ mLabeledByIds.add(mLabeledById);
+ }
}
/**
@@ -4242,6 +4380,10 @@ public class AccessibilityNodeInfo implements Parcelable {
fieldIndex++;
if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
+ if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
@@ -4383,6 +4525,18 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final LongArray labeledByIds = mLabeledByIds;
+ if (labeledByIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int labeledByIdsSize = labeledByIds.size();
+ parcel.writeInt(labeledByIdsSize);
+ for (int i = 0; i < labeledByIdsSize; i++) {
+ parcel.writeLong(labeledByIds.get(i));
+ }
+ }
+ }
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4550,6 +4704,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mParentNodeId = other.mParentNodeId;
mLabelForId = other.mLabelForId;
mLabeledById = other.mLabeledById;
+ mLabeledByIds = other.mLabeledByIds;
mTraversalBefore = other.mTraversalBefore;
mTraversalAfter = other.mTraversalAfter;
mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
@@ -4656,6 +4811,18 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int labeledByIdsSize = parcel.readInt();
+ if (labeledByIdsSize <= 0) {
+ mLabeledByIds = null;
+ } else {
+ mLabeledByIds = new LongArray(labeledByIdsSize);
+ for (int i = 0; i < labeledByIdsSize; i++) {
+ final long labeledById = parcel.readLong();
+ mLabeledByIds.add(labeledById);
+ }
+ }
+ }
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index b2810155098f..23d7732e469d 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1365,10 +1365,10 @@ public final class InputMethodManager {
ImeTracker.PHASE_CLIENT_HANDLE_SET_IME_VISIBILITY);
if (visible) {
insetsController.show(WindowInsets.Type.ime(),
- false /* fromIme */, null /* statsToken */);
+ false /* fromIme */, statsToken);
} else {
insetsController.hide(WindowInsets.Type.ime(),
- false /* fromIme */, null /* statsToken */);
+ false /* fromIme */, statsToken);
}
}
} else {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index fe2651030789..7366b9a443c3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -497,25 +497,32 @@ public abstract class WebSettings {
public abstract boolean getUseWebViewBackgroundForOverscrollBackground();
/**
- * Sets whether the WebView should save form data. In Android O, the
- * platform has implemented a fully functional Autofill feature to store
- * form data. Therefore, the Webview form data save feature is disabled.
+ * Sets whether the WebView should save form data. In {@link android.os.Build.VERSION_CODES#O},
+ * the platform has implemented a fully functional Autofill feature to store form data.
+ * Therefore, the Webview form data save feature is disabled.
*
- * Note that the feature will continue to be supported on older versions of
+ * <p>Note that the feature will continue to be supported on older versions of
* Android as before.
*
- * @deprecated In Android O and afterwards, this function does not have
- * any effect, the form data will be saved to platform's autofill service
- * if applicable.
+ * @see #getSaveFormData
+ * @deprecated In Android O and afterwards, this function does not have any effect. Form data
+ * will be saved to platform's autofill service if applicable.
*/
@Deprecated
public abstract void setSaveFormData(boolean save);
/**
- * Gets whether the WebView saves form data.
+ * Gets whether the WebView saves form data. In {@link android.os.Build.VERSION_CODES#O}, the
+ * platform has implemented a fully functional Autofill feature to store form data. Therefore,
+ * the Webview form data save feature is disabled.
+ *
+ * <p>Note that the feature will continue to be supported on older versions of
+ * Android as before.
*
* @return whether the WebView saves form data
* @see #setSaveFormData
+ * @deprecated In Android O and afterwards, this function does not have any effect. Form data
+ * will be filled from the platform's autofill service if applicable.
*/
@Deprecated
public abstract boolean getSaveFormData();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 72b268b440b5..1ea20fa85bd4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10627,7 +10627,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int startOffset = mLayout.getOffsetForHorizontal(line, point.x);
if (mLayout.isLevelBoundary(startOffset)) {
- // TODO(b/247551937): Support gesture at level boundaries.
+ // Gesture at level boundaries is not supported.
return handleGestureFailure(gesture);
}
diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl
index b1d75826a948..a8017762dcc1 100644
--- a/core/java/android/window/IBackAnimationRunner.aidl
+++ b/core/java/android/window/IBackAnimationRunner.aidl
@@ -38,14 +38,13 @@ oneway interface IBackAnimationRunner {
/**
* Called when the system is ready for the handler to start animating all the visible tasks.
* @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING)
- windows to animate,
- * @param wallpapers The list of wallpapers to animate.
- * @param nonApps The list of non-app windows such as Bubbles to animate.
+ * windows to animate,
+ * @param prepareOpenTransition If non-null, the animation should start after receive open
+ * transition
* @param finishedCallback The callback to invoke when the animation is finished.
*/
void onAnimationStart(
in RemoteAnimationTarget[] apps,
- in RemoteAnimationTarget[] wallpapers,
- in RemoteAnimationTarget[] nonApps,
+ in IBinder prepareOpenTransition,
in IBackAnimationFinishedCallback finishedCallback) = 2;
} \ 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 4f848175cd99..217bca77af0c 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -225,3 +225,13 @@ flag {
description: "Adds a minimize button the the caption bar"
bug: "356843241"
}
+
+flag {
+ name: "skip_compat_ui_education_in_desktop_mode"
+ namespace: "lse_desktop_experience"
+ description: "Ignore Compat UI educations when in Desktop Mode."
+ bug: "357062954"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index be744fde96dd..67fc27042ff8 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -19,16 +19,6 @@ flag {
}
flag {
- name: "do_not_skip_ime_by_target_visibility"
- namespace: "windowing_frontend"
- description: "Avoid window traversal missing IME"
- bug: "339375944"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "apply_lifecycle_on_pip_change"
namespace: "windowing_frontend"
description: "Make pip activity lifecyle change with windowing mode"
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 4ccdf79da358..cf3a54b8437f 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -109,45 +109,13 @@ public final class AccessibilityTargetHelper {
public static List<AccessibilityTarget> getInstalledTargets(Context context,
@UserShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
- targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
+ targets.addAll(getAccessibilityServiceTargets(context, shortcutType));
+ targets.addAll(getAccessibilityActivityTargets(context, shortcutType));
targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
return targets;
}
- private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
- @UserShortcutType int shortcutType) {
- final List<AccessibilityTarget> serviceTargets =
- getAccessibilityServiceTargets(context, shortcutType);
- final List<AccessibilityTarget> activityTargets =
- getAccessibilityActivityTargets(context, shortcutType);
-
- for (AccessibilityTarget activityTarget : activityTargets) {
- serviceTargets.removeIf(
- serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget));
- }
-
- final List<AccessibilityTarget> targets = new ArrayList<>();
- targets.addAll(serviceTargets);
- targets.addAll(activityTargets);
-
- return targets;
- }
-
- private static boolean arePackageNameAndLabelTheSame(@NonNull AccessibilityTarget serviceTarget,
- @NonNull AccessibilityTarget activityTarget) {
- final ComponentName serviceComponentName =
- ComponentName.unflattenFromString(serviceTarget.getId());
- final ComponentName activityComponentName =
- ComponentName.unflattenFromString(activityTarget.getId());
- final boolean isSamePackageName = activityComponentName.getPackageName().equals(
- serviceComponentName.getPackageName());
- final boolean isSameLabel = activityTarget.getLabel().equals(
- serviceTarget.getLabel());
-
- return isSamePackageName && isSameLabel;
- }
-
private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
@UserShortcutType int shortcutType) {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 1c26687c97e9..2e0ff3db6c50 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -34,6 +34,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
@@ -176,24 +177,19 @@ public final class ShortcutUtils {
* @param type The shortcut type.
* @return Mapping key in Settings.
*/
+ @SuppressLint("SwitchIntDef")
public static String convertToKey(@UserShortcutType int type) {
- switch (type) {
- case SOFTWARE:
- return Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
- case GESTURE:
- return Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS;
- case HARDWARE:
- return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
- case TRIPLETAP:
- return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
- case TWOFINGER_DOUBLETAP:
- return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
- case QUICK_SETTINGS:
- return Settings.Secure.ACCESSIBILITY_QS_TARGETS;
- default:
- throw new IllegalArgumentException(
- "Unsupported user shortcut type: " + type);
- }
+ return switch (type) {
+ case SOFTWARE -> Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
+ case GESTURE -> Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS;
+ case HARDWARE -> Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+ case TRIPLETAP -> Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+ case TWOFINGER_DOUBLETAP ->
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
+ case QUICK_SETTINGS -> Settings.Secure.ACCESSIBILITY_QS_TARGETS;
+ default -> throw new IllegalArgumentException(
+ "Unsupported user shortcut type: " + type);
+ };
}
/**
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 0d0207ff7c0e..e14249cc028b 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2513,9 +2513,16 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
+
+ // For floating windows that are *allowed* to fill the screen (like Wear) content
+ // should still be wrapped if they're not explicitly requested as fullscreen.
+ final boolean isFloatingAndFullscreen = mIsFloating
+ && mAllowFloatingWindowsFillScreen
+ && a.getBoolean(R.styleable.Window_windowFullscreen, false);
+
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
- if (mIsFloating && !mAllowFloatingWindowsFillScreen) {
+ if (mIsFloating && !isFloatingAndFullscreen) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
diff --git a/core/java/com/android/internal/protolog/IProtoLogClient.aidl b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
index 969ed99d3aca..64944f42374b 100644
--- a/core/java/com/android/internal/protolog/IProtoLogClient.aidl
+++ b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
@@ -20,7 +20,7 @@ package com.android.internal.protolog;
* The ProtoLog client interface.
*
* These clients will communicate bi-directionally with the ProtoLog service
- * (@see IProtoLogService.aidl) running in the system process.
+ * (@see IProtoLogConfigurationService.aidl) running in the system process.
*
* {@hide}
*/
diff --git a/core/java/com/android/internal/protolog/IProtoLogService.aidl b/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
index cc349ea2985a..ce948281bbd6 100644
--- a/core/java/com/android/internal/protolog/IProtoLogService.aidl
+++ b/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
@@ -40,7 +40,7 @@ import com.android.internal.protolog.IProtoLogClient;
*
* {@hide}
*/
-interface IProtoLogService {
+interface IProtoLogConfigurationService {
interface IRegisterClientArgs {
String[] getGroups();
boolean[] getGroupsDefaultLogcatStatus();
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index b82c660fcf57..34e04181388d 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -40,6 +40,8 @@ import java.util.List;
*/
@Deprecated
public class LogcatOnlyProtoLogImpl implements IProtoLog {
+ private static final String LOG_TAG = LogcatOnlyProtoLogImpl.class.getName();
+
@Override
public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
Object[] args) {
@@ -48,19 +50,21 @@ public class LogcatOnlyProtoLogImpl implements IProtoLog {
@Override
public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object[] args) {
- if (REQUIRE_PROTOLOGTOOL) {
- throw new RuntimeException(
- "REQUIRE_PROTOLOGTOOL not set to false before the first log call.");
+ if (REQUIRE_PROTOLOGTOOL && group.isLogToProto()) {
+ Log.w(LOG_TAG, "ProtoLog message not processed. Failed to log it to proto. "
+ + "Logging it below to logcat instead.");
}
- String formattedString = TextUtils.formatSimple(messageString, args);
- switch (logLevel) {
- case VERBOSE -> Log.v(group.getTag(), formattedString);
- case INFO -> Log.i(group.getTag(), formattedString);
- case DEBUG -> Log.d(group.getTag(), formattedString);
- case WARN -> Log.w(group.getTag(), formattedString);
- case ERROR -> Log.e(group.getTag(), formattedString);
- case WTF -> Log.wtf(group.getTag(), formattedString);
+ if (group.isLogToLogcat() || group.isLogToProto()) {
+ String formattedString = TextUtils.formatSimple(messageString, args);
+ switch (logLevel) {
+ case VERBOSE -> Log.v(group.getTag(), formattedString);
+ case INFO -> Log.i(group.getTag(), formattedString);
+ case DEBUG -> Log.d(group.getTag(), formattedString);
+ case WARN -> Log.w(group.getTag(), formattedString);
+ case ERROR -> Log.e(group.getTag(), formattedString);
+ case WTF -> Log.wtf(group.getTag(), formattedString);
+ }
}
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 5517967341b5..b2fdf17f8564 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -16,7 +16,7 @@
package com.android.internal.protolog;
-import static android.content.Context.PROTOLOG_SERVICE;
+import static android.content.Context.PROTOLOG_CONFIGURATION_SERVICE;
import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STACKTRACE;
import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STRING_ARGS;
import static android.internal.perfetto.protos.ProfileCommon.InternedString.IID;
@@ -114,7 +114,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
private final Runnable mCacheUpdater;
@Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
- private final IProtoLogService mProtoLogService;
+ private final IProtoLogConfigurationService mProtoLogConfigurationService;
@NonNull
private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
@@ -186,30 +186,32 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
registerGroupsLocally(groups);
if (android.tracing.Flags.clientSideProtoLogging()) {
- mProtoLogService =
- IProtoLogService.Stub.asInterface(ServiceManager.getService(PROTOLOG_SERVICE));
- Objects.requireNonNull(mProtoLogService,
+ mProtoLogConfigurationService =
+ IProtoLogConfigurationService.Stub.asInterface(ServiceManager.getService(
+ PROTOLOG_CONFIGURATION_SERVICE));
+ Objects.requireNonNull(mProtoLogConfigurationService,
"ServiceManager returned a null ProtoLog service");
try {
- var args = new ProtoLogService.RegisterClientArgs();
+ var args = new ProtoLogConfigurationService.RegisterClientArgs();
if (viewerConfigFilePath != null) {
args.setViewerConfigFile(viewerConfigFilePath);
}
final var groupArgs = Stream.of(groups)
- .map(group -> new ProtoLogService.RegisterClientArgs.GroupConfig(
- group.name(), group.isLogToLogcat()))
- .toArray(ProtoLogService.RegisterClientArgs.GroupConfig[]::new);
+ .map(group -> new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(group.name(), group.isLogToLogcat()))
+ .toArray(
+ ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new);
args.setGroups(groupArgs);
- mProtoLogService.registerClient(this, args);
+ mProtoLogConfigurationService.registerClient(this, args);
} catch (RemoteException e) {
throw new RuntimeException("Failed to register ProtoLog client");
}
} else {
- mProtoLogService = null;
+ mProtoLogConfigurationService = null;
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 660d3c99f538..bf77db7b6a33 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -63,6 +63,9 @@ public class ProtoLog {
* @param groups The ProtoLog groups that will be used in the process.
*/
public static void init(IProtoLogGroup... groups) {
+ // These tracing instances are only used when we cannot or do not preprocess the source
+ // files to extract out the log strings. Otherwise, the trace calls are replaced with calls
+ // directly to the generated tracing implementations.
if (android.tracing.Flags.perfettoProtologTracing()) {
synchronized (sInitLock) {
if (sProtoLogInstance != null) {
@@ -76,8 +79,6 @@ public class ProtoLog {
sProtoLogInstance = new PerfettoProtoLogImpl(groups);
}
} else {
- // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
- // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
sProtoLogInstance = new LogcatOnlyProtoLogImpl();
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
index 3dab2e39b852..82d8d3431a9d 100644
--- a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
+++ b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
@@ -29,18 +29,20 @@ import java.util.Set;
public class ProtoLogCommandHandler extends ShellCommand {
@NonNull
- private final ProtoLogService mProtoLogService;
+ private final ProtoLogConfigurationService mProtoLogConfigurationService;
@Nullable
private final PrintWriter mPrintWriter;
- public ProtoLogCommandHandler(@NonNull ProtoLogService protoLogService) {
- this(protoLogService, null);
+ public ProtoLogCommandHandler(
+ @NonNull ProtoLogConfigurationService protoLogConfigurationService) {
+ this(protoLogConfigurationService, null);
}
@VisibleForTesting
public ProtoLogCommandHandler(
- @NonNull ProtoLogService protoLogService, @Nullable PrintWriter printWriter) {
- this.mProtoLogService = protoLogService;
+ @NonNull ProtoLogConfigurationService protoLogConfigurationService,
+ @Nullable PrintWriter printWriter) {
+ this.mProtoLogConfigurationService = protoLogConfigurationService;
this.mPrintWriter = printWriter;
}
@@ -94,7 +96,7 @@ public class ProtoLogCommandHandler extends ShellCommand {
switch (cmd) {
case "list": {
- final String[] availableGroups = mProtoLogService.getGroups();
+ final String[] availableGroups = mProtoLogConfigurationService.getGroups();
if (availableGroups.length == 0) {
pw.println("No ProtoLog groups registered with ProtoLog service.");
return 0;
@@ -117,12 +119,13 @@ public class ProtoLogCommandHandler extends ShellCommand {
pw.println("ProtoLog group " + group + "'s status:");
- if (!Set.of(mProtoLogService.getGroups()).contains(group)) {
+ if (!Set.of(mProtoLogConfigurationService.getGroups()).contains(group)) {
pw.println("UNREGISTERED");
return 0;
}
- pw.println("LOG_TO_LOGCAT = " + mProtoLogService.isLoggingToLogcat(group));
+ pw.println("LOG_TO_LOGCAT = "
+ + mProtoLogConfigurationService.isLoggingToLogcat(group));
return 0;
}
default: {
@@ -142,11 +145,11 @@ public class ProtoLogCommandHandler extends ShellCommand {
switch (cmd) {
case "enable" -> {
- mProtoLogService.enableProtoLogToLogcat(processGroups());
+ mProtoLogConfigurationService.enableProtoLogToLogcat(processGroups());
return 0;
}
case "disable" -> {
- mProtoLogService.disableProtoLogToLogcat(processGroups());
+ mProtoLogConfigurationService.disableProtoLogToLogcat(processGroups());
return 0;
}
default -> {
@@ -159,7 +162,7 @@ public class ProtoLogCommandHandler extends ShellCommand {
@NonNull
private String[] processGroups() {
if (getRemainingArgsCount() == 0) {
- return mProtoLogService.getGroups();
+ return mProtoLogConfigurationService.getGroups();
}
final List<String> groups = new ArrayList<>();
diff --git a/core/java/com/android/internal/protolog/ProtoLogService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
index 2333a062d897..176573870679 100644
--- a/core/java/com/android/internal/protolog/ProtoLogService.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
@@ -70,9 +70,9 @@ import java.util.TreeMap;
* <p>
* This service is intended to run on the system server, such that it never gets frozen.
*/
-@SystemService(Context.PROTOLOG_SERVICE)
-public final class ProtoLogService extends IProtoLogService.Stub {
- private static final String LOG_TAG = "ProtoLogService";
+@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE)
+public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub {
+ private static final String LOG_TAG = "ProtoLogConfigurationService";
private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
this::onTracingInstanceStart,
@@ -114,12 +114,12 @@ public final class ProtoLogService extends IProtoLogService.Stub {
private final ViewerConfigFileTracer mViewerConfigFileTracer;
- public ProtoLogService() {
- this(ProtoLogService::dumpTransitionTraceConfig);
+ public ProtoLogConfigurationService() {
+ this(ProtoLogConfigurationService::dumpTransitionTraceConfig);
}
@VisibleForTesting
- public ProtoLogService(@NonNull ViewerConfigFileTracer tracer) {
+ public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) {
// Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be
// receive the lifecycle callbacks of the datasource and write the viewer configs if and
// when required to the datasource.
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 6dc6585b5caf..5c06b87f78ac 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -17,6 +17,7 @@
package com.android.internal.protolog;
import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.DEFAULT;
+import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.DEFAULT_LOG_FROM_LEVEL;
import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.ENABLE_ALL;
import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.GROUP_OVERRIDES;
import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.TRACING_MODE;
@@ -43,7 +44,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
ProtoLogDataSource.TlsState,
@@ -190,73 +190,54 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
final Map<String, GroupConfig> groupConfigs = new HashMap<>();
while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (configStream.getFieldNumber() == (int) TRACING_MODE) {
- int tracingMode = configStream.readInt(TRACING_MODE);
- switch (tracingMode) {
- case DEFAULT:
- break;
- case ENABLE_ALL:
- defaultLogFromLevel = LogLevel.DEBUG;
- break;
- default:
- throw new RuntimeException("Unhandled ProtoLog tracing mode type");
- }
- }
- if (configStream.getFieldNumber() == (int) GROUP_OVERRIDES) {
- final long group_overrides_token = configStream.start(GROUP_OVERRIDES);
-
- String tag = null;
- LogLevel logFromLevel = defaultLogFromLevel;
- boolean collectStackTrace = false;
- while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (configStream.getFieldNumber() == (int) GROUP_NAME) {
- tag = configStream.readString(GROUP_NAME);
+ switch (configStream.getFieldNumber()) {
+ case (int) DEFAULT_LOG_FROM_LEVEL:
+ int defaultLogFromLevelInt = configStream.readInt(DEFAULT_LOG_FROM_LEVEL);
+ if (defaultLogFromLevelInt < defaultLogFromLevel.ordinal()) {
+ defaultLogFromLevel =
+ logLevelFromInt(configStream.readInt(DEFAULT_LOG_FROM_LEVEL));
}
- if (configStream.getFieldNumber() == (int) LOG_FROM) {
- final int logFromInt = configStream.readInt(LOG_FROM);
- switch (logFromInt) {
- case (ProtologCommon.PROTOLOG_LEVEL_DEBUG): {
- logFromLevel = LogLevel.DEBUG;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_VERBOSE): {
- logFromLevel = LogLevel.VERBOSE;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_INFO): {
- logFromLevel = LogLevel.INFO;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_WARN): {
- logFromLevel = LogLevel.WARN;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_ERROR): {
- logFromLevel = LogLevel.ERROR;
- break;
- }
- case (ProtologCommon.PROTOLOG_LEVEL_WTF): {
- logFromLevel = LogLevel.WTF;
- break;
- }
- default: {
- throw new RuntimeException("Unhandled log level");
- }
- }
+ break;
+ case (int) TRACING_MODE:
+ int tracingMode = configStream.readInt(TRACING_MODE);
+ switch (tracingMode) {
+ case DEFAULT:
+ break;
+ case ENABLE_ALL:
+ defaultLogFromLevel = LogLevel.DEBUG;
+ break;
+ default:
+ throw new RuntimeException("Unhandled ProtoLog tracing mode type");
}
- if (configStream.getFieldNumber() == (int) COLLECT_STACKTRACE) {
- collectStackTrace = configStream.readBoolean(COLLECT_STACKTRACE);
+ break;
+ case (int) GROUP_OVERRIDES:
+ final long group_overrides_token = configStream.start(GROUP_OVERRIDES);
+
+ String tag = null;
+ LogLevel logFromLevel = defaultLogFromLevel;
+ boolean collectStackTrace = false;
+ while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.getFieldNumber() == (int) GROUP_NAME) {
+ tag = configStream.readString(GROUP_NAME);
+ }
+ if (configStream.getFieldNumber() == (int) LOG_FROM) {
+ final int logFromInt = configStream.readInt(LOG_FROM);
+ logFromLevel = logLevelFromInt(logFromInt);
+ }
+ if (configStream.getFieldNumber() == (int) COLLECT_STACKTRACE) {
+ collectStackTrace = configStream.readBoolean(COLLECT_STACKTRACE);
+ }
}
- }
- if (tag == null) {
- throw new RuntimeException("Failed to decode proto config. "
- + "Got a group override without a group tag.");
- }
+ if (tag == null) {
+ throw new RuntimeException("Failed to decode proto config. "
+ + "Got a group override without a group tag.");
+ }
- groupConfigs.put(tag, new GroupConfig(logFromLevel, collectStackTrace));
+ groupConfigs.put(tag, new GroupConfig(logFromLevel, collectStackTrace));
- configStream.end(group_overrides_token);
+ configStream.end(group_overrides_token);
+ break;
}
}
@@ -265,6 +246,18 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
return new ProtoLogConfig(defaultLogFromLevel, groupConfigs);
}
+ private LogLevel logLevelFromInt(int logFromInt) {
+ return switch (logFromInt) {
+ case (ProtologCommon.PROTOLOG_LEVEL_DEBUG) -> LogLevel.DEBUG;
+ case (ProtologCommon.PROTOLOG_LEVEL_VERBOSE) -> LogLevel.VERBOSE;
+ case (ProtologCommon.PROTOLOG_LEVEL_INFO) -> LogLevel.INFO;
+ case (ProtologCommon.PROTOLOG_LEVEL_WARN) -> LogLevel.WARN;
+ case (ProtologCommon.PROTOLOG_LEVEL_ERROR) -> LogLevel.ERROR;
+ case (ProtologCommon.PROTOLOG_LEVEL_WTF) -> LogLevel.WTF;
+ default -> throw new RuntimeException("Unhandled log level");
+ };
+ }
+
public static class Instance extends DataSourceInstance {
public interface TracingInstanceStartCallback {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 1d43f6f083e6..c834dde56bfe 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -209,11 +209,6 @@ oneway interface IStatusBar
void onDisplayReady(int displayId);
/**
- * Notifies System UI whether the recents animation is running or not.
- */
- void onRecentsAnimationStateChanged(boolean running);
-
- /**
* Notifies System UI side of system bar attribute change on the specified display.
*
* @param displayId the ID of the display to notify.
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 76ce452858ad..f8a143693c83 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -16,6 +16,7 @@
package com.android.internal.statusbar;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,7 +24,18 @@ import android.os.UserHandle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+/**
+ * Representation of an icon that should appear in the status bar.
+ *
+ * <p>This includes notifications, conversations, and icons displayed on the right side (e.g.
+ * Wifi, Vibration/Silence, Priority Modes, etc).
+ *
+ * <p>This class is {@link Parcelable} but the {@link #preloadedIcon} is not (and will be lost if
+ * the object is copied through parcelling). If {@link #preloadedIcon} is supplied, it must match
+ * the {@link #icon} resource/bitmap.
+ */
public class StatusBarIcon implements Parcelable {
public enum Type {
// Notification: the sender avatar for important conversations
@@ -34,7 +46,9 @@ public class StatusBarIcon implements Parcelable {
// Notification: the small icon from the notification
NotifSmallIcon,
// The wi-fi, cellular or battery icon.
- SystemIcon
+ SystemIcon,
+ // Some other icon, corresponding to a resource (possibly in a different package).
+ ResourceIcon
}
public UserHandle user;
@@ -46,6 +60,13 @@ public class StatusBarIcon implements Parcelable {
public CharSequence contentDescription;
public Type type;
+ /**
+ * Optional {@link Drawable} corresponding to {@link #icon}. This field is not parcelable, so
+ * will be lost if the object is sent to a different process. If you set it, make sure to
+ * <em>also</em> set {@link #icon} pointing to the corresponding resource.
+ */
+ @Nullable public Drawable preloadedIcon;
+
public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number,
CharSequence contentDescription, Type type) {
if (icon.getType() == Icon.TYPE_RESOURCE
@@ -88,6 +109,7 @@ public class StatusBarIcon implements Parcelable {
StatusBarIcon that = new StatusBarIcon(this.user, this.pkg, this.icon,
this.iconLevel, this.number, this.contentDescription, this.type);
that.visible = this.visible;
+ that.preloadedIcon = this.preloadedIcon;
return that;
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 1e2cad41065d..1e965c5db7ae 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -49,81 +49,41 @@ public class ArrayUtils {
private ArrayUtils() { /* cannot be instantiated */ }
- @android.ravenwood.annotation.RavenwoodReplace
public static byte[] newUnpaddedByteArray(int minLen) {
return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen);
}
- @android.ravenwood.annotation.RavenwoodReplace
public static char[] newUnpaddedCharArray(int minLen) {
return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen);
}
- @android.ravenwood.annotation.RavenwoodReplace
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static int[] newUnpaddedIntArray(int minLen) {
return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen);
}
- @android.ravenwood.annotation.RavenwoodReplace
public static boolean[] newUnpaddedBooleanArray(int minLen) {
return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen);
}
- @android.ravenwood.annotation.RavenwoodReplace
public static long[] newUnpaddedLongArray(int minLen) {
return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen);
}
- @android.ravenwood.annotation.RavenwoodReplace
public static float[] newUnpaddedFloatArray(int minLen) {
return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen);
}
- @android.ravenwood.annotation.RavenwoodReplace
public static Object[] newUnpaddedObjectArray(int minLen) {
return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen);
}
- @android.ravenwood.annotation.RavenwoodReplace
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@SuppressWarnings("unchecked")
public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen);
}
- public static byte[] newUnpaddedByteArray$ravenwood(int minLen) {
- return new byte[minLen];
- }
-
- public static char[] newUnpaddedCharArray$ravenwood(int minLen) {
- return new char[minLen];
- }
-
- public static int[] newUnpaddedIntArray$ravenwood(int minLen) {
- return new int[minLen];
- }
-
- public static boolean[] newUnpaddedBooleanArray$ravenwood(int minLen) {
- return new boolean[minLen];
- }
-
- public static long[] newUnpaddedLongArray$ravenwood(int minLen) {
- return new long[minLen];
- }
-
- public static float[] newUnpaddedFloatArray$ravenwood(int minLen) {
- return new float[minLen];
- }
-
- public static Object[] newUnpaddedObjectArray$ravenwood(int minLen) {
- return new Object[minLen];
- }
-
- public static <T> T[] newUnpaddedArray$ravenwood(Class<T> clazz, int minLen) {
- return (T[]) Array.newInstance(clazz, minLen);
- }
-
/**
* Checks if the beginnings of two byte arrays are equal.
*
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index bc729f10d74c..b6383d9f0754 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
@@ -437,9 +437,8 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup {
// Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
// landscape.
- final int x = Math.min(
- contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
- mViewPortOnScreen.right - mPopupWindow.getWidth());
+ final int x = Math.clamp(contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
+ mViewPortOnScreen.left, mViewPortOnScreen.right - mPopupWindow.getWidth());
final int y;
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 50832a5c256a..8dd63cc07b8a 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -256,9 +256,21 @@ static void SurfaceTexture_classInit(JNIEnv* env, jclass clazz)
}
}
-static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
- jint texName, jboolean singleBufferMode, jobject weakThiz)
-{
+static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, jint texName,
+ jboolean singleBufferMode, jobject weakThiz) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ sp<SurfaceTexture> surfaceTexture;
+ if (isDetached) {
+ surfaceTexture = new SurfaceTexture(GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
+ } else {
+ surfaceTexture =
+ new SurfaceTexture(texName, GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
+ }
+
+ if (singleBufferMode) {
+ surfaceTexture->setMaxBufferCount(1);
+ }
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
@@ -275,6 +287,7 @@ static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
surfaceTexture = new SurfaceTexture(consumer, texName,
GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
if (surfaceTexture == 0) {
jniThrowException(env, OutOfResourcesException,
@@ -287,11 +300,27 @@ static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
createProcessUniqueId()));
// If the current context is protected, inform the producer.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ surfaceTexture->setConsumerIsProtected(isProtectedContext());
+
+ SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
+ sp<Surface> surface = surfaceTexture->getSurface();
+ if (nullptr == surface) {
+ jniThrowException(env, IllegalStateException, "Unable to get surface from SurfaceTexture");
+ return;
+ }
+ sp<IGraphicBufferProducer> igbp = surface->getIGraphicBufferProducer();
+ if (nullptr == igbp) {
+ jniThrowException(env, IllegalStateException, "Unable to get IGBP from Surface");
+ return;
+ }
+ SurfaceTexture_setProducer(env, thiz, igbp);
+#else
consumer->setConsumerIsProtected(isProtectedContext());
SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
SurfaceTexture_setProducer(env, thiz, producer);
-
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
jclass clazz = env->GetObjectClass(thiz);
if (clazz == NULL) {
jniThrowRuntimeException(env,
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
index 8de54586ab73..78cf6f464558 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -40,4 +40,5 @@ message ApplicationStartInfoProto {
optional bytes start_intent = 10;
optional AppStartLaunchMode launch_mode = 11;
optional bool was_force_stopped = 12;
+ optional int64 monotonic_creation_time_ms = 13;
}
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 58f39a9208da..42c591be60cc 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -1045,7 +1045,7 @@ message AppsExitInfoProto {
repeated Package packages = 2;
}
-// sync with com.android.server.am.am.ProcessList.java
+// LINT.IfChange
message AppsStartInfoProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -1064,4 +1064,6 @@ message AppsStartInfoProto {
repeated User users = 2;
}
repeated Package packages = 2;
+ optional int64 monotonic_time = 3;
}
+// LINT.ThenChange(/services/core/java/com/android/server/am/AppStartInfoTracker.java)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2dd560c69a8c..91c33702d3e3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3836,7 +3836,6 @@
<!-- Allows an application to use audit logging API.
@hide
@SystemApi
- @FlaggedApi("android.app.admin.flags.security_log_v2_enabled")
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"
android:protectionLevel="internal|role" />
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 118acacc5e52..a7240ffc6012 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -443,4 +443,20 @@
<!-- Telephony satellite gateway intent for handling carrier roaming to satellite is using ESOS messaging. -->
<string name="config_satellite_carrier_roaming_esos_provisioned_intent_action" translatable="false"></string>
<java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_intent_action" />
+
+ <!-- The time duration in minutes to wait before retry validating a possible change
+ in satellite allowed region. The default value is 10 minutes. -->
+ <integer name="config_satellite_delay_minutes_before_retry_validating_possible_change_in_allowed_region">10</integer>
+ <java-symbol type="integer" name="config_satellite_delay_minutes_before_retry_validating_possible_change_in_allowed_region" />
+
+ <!-- The maximum retry count to validate a possible change in satellite allowed region.
+ The default value is 3 minutes. -->
+ <integer name="config_satellite_max_retry_count_for_validating_possible_change_in_allowed_region">3</integer>
+ <java-symbol type="integer" name="config_satellite_max_retry_count_for_validating_possible_change_in_allowed_region" />
+
+ <!-- The time duration in minutes for location query throttle interval.
+ The default value is 10 minutes. -->
+ <integer name="config_satellite_location_query_throttle_interval_minutes">10</integer>
+ <java-symbol type="integer" name="config_satellite_location_query_throttle_interval_minutes" />
+
</resources>
diff --git a/core/res/res/values/config_tv_external_input_logging.xml b/core/res/res/values/config_tv_external_input_logging.xml
index 72e30be00a02..293a183ffeca 100644
--- a/core/res/res/values/config_tv_external_input_logging.xml
+++ b/core/res/res/values/config_tv_external_input_logging.xml
@@ -24,27 +24,39 @@
entries do not follow the convention, but all new entries should. -->
<resources>
- <bool name="config_tvExternalInputLoggingDisplayNameFilterEnabled">false</bool>
+ <bool name="config_tvExternalInputLoggingDisplayNameFilterEnabled">true</bool>
<string-array name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames">
- <item>Chromecast</item>
+ <item>ADT-4</item>
<item>Chromecast HD</item>
- <item>SHIELD</item>
- <item>Roku</item>
- <item>Roku Express 4</item>
- <item>Home Theater</item>
<item>Fire TV Stick</item>
- <item>PlayStation 5</item>
+ <item>Freebox Player</item>
+ <item>Home Theater</item>
+ <item>Jarvis</item>
<item>NintendoSwitch</item>
+ <item>onn. 4K Plus S</item>
+ <item>onn. Streaming</item>
+ <item>PlayStation 4</item>
+ <item>PlayStation 5</item>
+ <item>Roku 3</item>
+ <item>Roku Express 4</item>
</string-array>
<string-array name="config_tvExternalInputLoggingDeviceBrandNames">
- <item>Chromecast</item>
- <item>SHIELD</item>
- <item>Roku</item>
<item>Apple</item>
+ <item>Chromecast</item>
<item>Fire TV</item>
- <item>PlayStation</item>
+ <item>Freebox</item>
+ <item>Google</item>
+ <item>MiBOX</item>
+ <item>Microsoft</item>
<item>Nintendo</item>
+ <item>NVIDIA</item>
+ <item>onn.</item>
+ <item>PlayStation</item>
+ <item>Roku</item>
+ <item>SHIELD</item>
+ <item>Sony</item>
+ <item>XBOX</item>
</string-array>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9a52bd4d266e..cf0fc6159f42 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5466,6 +5466,8 @@
<!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
<java-symbol type="xml" name="haptic_feedback_customization" />
+ <java-symbol type="xml" name="haptic_feedback_customization_source_rotary_encoder" />
+ <java-symbol type="xml" name="haptic_feedback_customization_source_touchscreen" />
<!-- For ActivityManager PSS profiling configurability -->
<java-symbol type="bool" name="config_am_disablePssProfiling" />
diff --git a/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml b/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
new file mode 100644
index 000000000000..7ac0787ab7a0
--- /dev/null
+++ b/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<haptic-feedback-constants/>
diff --git a/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml b/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
new file mode 100644
index 000000000000..7ac0787ab7a0
--- /dev/null
+++ b/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<haptic-feedback-constants/>
diff --git a/core/tests/coretests/src/android/util/StateSetTest.java b/core/tests/coretests/src/android/util/StateSetTest.java
index 14e4e2000a65..c9df83d84f3e 100644
--- a/core/tests/coretests/src/android/util/StateSetTest.java
+++ b/core/tests/coretests/src/android/util/StateSetTest.java
@@ -19,7 +19,6 @@ package android.util;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -33,7 +32,6 @@ import org.junit.runner.RunWith;
* Tests for {@link StateSet}
*/
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = StateSet.class)
public class StateSetTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 668487dcc490..786f1e84728d 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -40,6 +40,7 @@ import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
import android.view.SurfaceControl.Transaction;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
+import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -117,11 +118,21 @@ public class InsetsAnimationControlImplTest {
SparseArray<InsetsSourceControl> controls = new SparseArray<>();
controls.put(ID_STATUS_BAR, topConsumer.getControl());
controls.put(ID_NAVIGATION_BAR, navConsumer.getControl());
+ InsetsAnimationSpec spec = new InsetsAnimationSpec() {
+ @Override
+ public long getDurationMs(boolean hasZeroInsetsIme) {
+ return 10;
+ }
+ @Override
+ public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
+ return new LinearInterpolator();
+ }
+ };
+
mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- mMockController, 10 /* durationMs */, new LinearInterpolator(),
- 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
- null /* statsToken */);
+ mMockController, spec /* insetsAnimationSpecCreator */, 0 /* animationType */,
+ 0 /* layoutInsetsDuringAnimation */, null /* translator */, null /* statsToken */);
mController.setReadyDispatched(true);
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 7bc0d2f2fcd3..ce7e85868e8c 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -69,6 +69,7 @@ import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.widget.TextView;
@@ -136,7 +137,7 @@ public class InsetsControllerTest {
mTestHandler = new TestHandler(null, mTestClock);
mTestHost = spy(new TestHost(mViewRoot));
mController = new InsetsController(mTestHost, (controller, id, type) -> {
- if (type == ime()) {
+ if (!Flags.refactorInsetsController() && type == ime()) {
return new InsetsSourceConsumer(id, type, controller.getState(),
Transaction::new, controller) {
@@ -260,7 +261,11 @@ public class InsetsControllerTest {
mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
// When using the animation thread, this must not invoke onReady()
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
});
@@ -277,7 +282,12 @@ public class InsetsControllerTest {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
mController.show(all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
@@ -299,7 +309,11 @@ public class InsetsControllerTest {
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations();
assertTrue(isRequestedVisible(mController, ime()));
mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty());
@@ -469,7 +483,12 @@ public class InsetsControllerTest {
assertFalse(mController.getState().peekSource(ID_IME).isVisible());
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
// Gaining control shortly after
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
@@ -493,7 +512,12 @@ public class InsetsControllerTest {
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
@@ -558,7 +582,13 @@ public class InsetsControllerTest {
@Test
public void testControlImeNotReady() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -571,7 +601,13 @@ public class InsetsControllerTest {
verify(listener, never()).onReady(any(), anyInt());
// Pretend that IME is calling.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ // Send the IME control with leash, so that the animation can start
+ InsetsSourceControl ime = createControl(ID_IME, ime(), true /* hasLeash */);
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -583,7 +619,13 @@ public class InsetsControllerTest {
@Test
public void testControlImeNotReady_controlRevoked() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -604,7 +646,13 @@ public class InsetsControllerTest {
@Test
public void testControlImeNotReady_timeout() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -655,7 +703,11 @@ public class InsetsControllerTest {
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
copy.peekSource(ID_IME).setFrame(0, 1, 2, 3);
@@ -886,7 +938,11 @@ public class InsetsControllerTest {
// Showing invisible ime should only causes insets change once.
clearInvocations(mTestHost);
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
verify(mTestHost, times(1)).notifyInsetsChanged();
// Sending the same insets state should not cause insets change.
@@ -953,7 +1009,11 @@ public class InsetsControllerTest {
assertNull(imeInsetsConsumer.getControl());
// Verify IME requested visibility should be updated to IME consumer from controller.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
assertTrue(isRequestedVisible(mController, ime()));
mController.hide(ime());
@@ -966,7 +1026,11 @@ public class InsetsControllerTest {
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -990,8 +1054,13 @@ public class InsetsControllerTest {
public void testImeShowRequestCancelsPredictiveBackPostCommitAnim() {
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1008,12 +1077,20 @@ public class InsetsControllerTest {
assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
// verify show request is ignored during pre commit phase of predictive back anim
- mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ } else {
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
// verify show request is applied during post commit phase of predictive back anim
mController.setPredictiveBackImeHideAnimInProgress(true);
- mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ } else {
+ mController.show(ime(), false /* fromIme */, null /* statsToken */);
+ }
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
// additionally verify that IME ends up visible
@@ -1027,7 +1104,11 @@ public class InsetsControllerTest {
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1058,11 +1139,15 @@ public class InsetsControllerTest {
}
private InsetsSourceControl createControl(int id, @InsetsType int type) {
+ return createControl(id, type, true);
+ }
+
+ private InsetsSourceControl createControl(int id, @InsetsType int type, boolean hasLeash) {
// Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
// attempt to release mLeash directly.
SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
- return new InsetsSourceControl(id, type, copy,
+ return new InsetsSourceControl(id, type, hasLeash ? copy : null,
(type & WindowInsets.Type.defaultVisible()) != 0, new Point(), Insets.NONE);
}
@@ -1071,9 +1156,13 @@ public class InsetsControllerTest {
}
private InsetsSourceControl[] prepareControls() {
+ return prepareControls(true);
+ }
+
+ private InsetsSourceControl[] prepareControls(boolean imeControlHasLeash) {
final InsetsSourceControl navBar = createControl(ID_NAVIGATION_BAR, navigationBars());
final InsetsSourceControl statusBar = createControl(ID_STATUS_BAR, statusBars());
- final InsetsSourceControl ime = createControl(ID_IME, ime());
+ final InsetsSourceControl ime = createControl(ID_IME, ime(), imeControlHasLeash);
InsetsSourceControl[] controls = new InsetsSourceControl[3];
controls[0] = navBar;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index a5137bdf80b8..6e563ff44478 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@ public class AccessibilityNodeInfoTest {
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 43;
+ private static final int NUM_MARSHALLED_PROPERTIES = 44;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
index a895378eaaf9..b183ecb50591 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
@@ -18,6 +18,8 @@ package com.android.internal.statusbar;
import static com.google.common.truth.Truth.assertThat;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.os.Parcel;
import android.os.UserHandle;
@@ -37,18 +39,55 @@ public class StatusBarIconTest {
*/
@Test
public void testParcelable() {
+ final StatusBarIcon original = newStatusBarIcon();
+
+ final StatusBarIcon copy = parcelAndUnparcel(original);
+
+ assertSerializableFieldsEqual(copy, original);
+ }
+
+ @Test
+ public void testClone_withPreloaded() {
+ final StatusBarIcon original = newStatusBarIcon();
+ original.preloadedIcon = new ColorDrawable(Color.RED);
+
+ final StatusBarIcon copy = original.clone();
+
+ assertSerializableFieldsEqual(copy, original);
+ assertThat(copy.preloadedIcon).isNotNull();
+ assertThat(copy.preloadedIcon).isInstanceOf(ColorDrawable.class);
+ assertThat(((ColorDrawable) copy.preloadedIcon).getColor()).isEqualTo(Color.RED);
+ }
+
+ @Test
+ public void testClone_noPreloaded() {
+ final StatusBarIcon original = newStatusBarIcon();
+
+ final StatusBarIcon copy = original.clone();
+
+ assertSerializableFieldsEqual(copy, original);
+ assertThat(copy.preloadedIcon).isEqualTo(original.preloadedIcon);
+ }
+
+
+ private static StatusBarIcon newStatusBarIcon() {
final UserHandle dummyUserHandle = UserHandle.of(100);
final String dummyIconPackageName = "com.android.internal.statusbar.test";
final int dummyIconId = 123;
final int dummyIconLevel = 1;
final int dummyIconNumber = 2;
final CharSequence dummyIconContentDescription = "dummyIcon";
- final StatusBarIcon original = new StatusBarIcon(dummyIconPackageName, dummyUserHandle,
- dummyIconId, dummyIconLevel, dummyIconNumber, dummyIconContentDescription,
+ return new StatusBarIcon(
+ dummyIconPackageName,
+ dummyUserHandle,
+ dummyIconId,
+ dummyIconLevel,
+ dummyIconNumber,
+ dummyIconContentDescription,
StatusBarIcon.Type.SystemIcon);
+ }
- final StatusBarIcon copy = clone(original);
-
+ private static void assertSerializableFieldsEqual(StatusBarIcon copy, StatusBarIcon original) {
assertThat(copy.user).isEqualTo(original.user);
assertThat(copy.pkg).isEqualTo(original.pkg);
assertThat(copy.icon.sameAs(original.icon)).isTrue();
@@ -56,19 +95,17 @@ public class StatusBarIconTest {
assertThat(copy.visible).isEqualTo(original.visible);
assertThat(copy.number).isEqualTo(original.number);
assertThat(copy.contentDescription).isEqualTo(original.contentDescription);
+ assertThat(copy.type).isEqualTo(original.type);
}
- private StatusBarIcon clone(StatusBarIcon original) {
- Parcel parcel = null;
+ private static StatusBarIcon parcelAndUnparcel(StatusBarIcon original) {
+ Parcel parcel = Parcel.obtain();
try {
- parcel = Parcel.obtain();
original.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
return StatusBarIcon.CREATOR.createFromParcel(parcel);
} finally {
- if (parcel != null) {
- parcel.recycle();
- }
+ parcel.recycle();
}
}
}
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 1a3a0f60f2dc..cbaac21aa10b 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -71,6 +71,8 @@ prebuilt_fonts_xml {
},
}
+// TODO(nona): Change this to use generate_font_fallback to be able to generate XML from
+// per family JSON config
prebuilt_fonts_xml {
name: "font_fallback.xml",
src: "font_fallback.xml",
@@ -94,3 +96,33 @@ filegroup {
"DroidSansMono.ttf",
],
}
+
+genrule {
+ name: "generate_font_fallback",
+ tools: [":generate_fonts_xml"],
+ tool_files: [
+ "alias.json",
+ "fallback_order.json",
+ ],
+ srcs: [
+ ":CarroisGothicSC",
+ ":ComingSoon",
+ ":CutiveMono",
+ ":DancingScript",
+ ":DroidSansMono",
+ ":Roboto",
+ ":RobotoFlex",
+ ":SourceSansPro",
+ ":noto-fonts",
+ ],
+ exclude_srcs: [
+ "alias.json",
+ "fallback_order.json",
+ ],
+ out: ["font_fallback.xml"],
+ cmd: "$(location :generate_fonts_xml) " +
+ "--alias=$(location alias.json) " +
+ "--fallback=$(location fallback_order.json) " +
+ "$(in) " +
+ "-o $(out)",
+}
diff --git a/data/fonts/alias.json b/data/fonts/alias.json
new file mode 100644
index 000000000000..b5b867a49259
--- /dev/null
+++ b/data/fonts/alias.json
@@ -0,0 +1,37 @@
+[
+ // sans-serif aliases
+ { "name": "arial", "to": "sans-serif" },
+ { "name": "helvetica", "to": "sans-serif" },
+ { "name": "tahoma", "to": "sans-serif" },
+ { "name": "verdana", "to": "sans-serif" },
+ { "name": "sans-serif-black", "to": "sans-serif", "weight": "900" },
+ { "name": "sans-serif-light", "to": "sans-serif", "weight": "300" },
+ { "name": "sans-serif-medium", "to": "sans-serif", "weight": "500" },
+ { "name": "sans-serif-thin", "to": "sans-serif", "weight": "100" },
+
+ // sans-serif-condensed aliases
+ { "name": "sans-serif-condensed-light", "to": "sans-serif-condensed", "weight": "300" },
+ { "name": "sans-serif-condensed-medium", "to": "sans-serif-condensed", "weight": "500" },
+
+ // serif aliases
+ { "name": "ITC Stone Serif", "to": "serif" },
+ { "name": "baskerville", "to": "serif" },
+ { "name": "fantasy", "to": "serif" },
+ { "name": "georgia", "to": "serif" },
+ { "name": "goudy", "to": "serif" },
+ { "name": "palatino", "to": "serif" },
+ { "name": "times new roman", "to": "serif" },
+ { "name": "times", "to": "serif" },
+ { "name": "serif-bold", "to": "serif", "weight": "700" },
+
+ // monospace aliases
+ { "name": "monaco", "to": "monospace" },
+ { "name": "sans-serif-monospace", "to": "monospace" },
+
+ // serif-monospace aliases
+ { "name": "courier new", "to": "serif-monospace" },
+ { "name": "courier", "to": "serif-monospace" },
+
+ // source-sans-pro aliases
+ { "name": "source-sans-pro-semi-bold", "to": "source-sans-pro", "weight": "600" }
+]
diff --git a/data/fonts/fallback_order.json b/data/fonts/fallback_order.json
new file mode 100644
index 000000000000..2fc3f3e3fc5b
--- /dev/null
+++ b/data/fonts/fallback_order.json
@@ -0,0 +1,136 @@
+[
+ { "lang": "und-Arab" },
+ { "lang": "und-Ethi" },
+ { "lang": "und-Hebr" },
+ { "lang": "und-Thai" },
+ { "lang": "und-Armn" },
+ { "lang": "und-Geor,und-Geok" },
+ { "lang": "und-Deva" },
+ { "lang": "und-Gujr" },
+ { "lang": "und-Guru" },
+ { "lang": "und-Taml" },
+ { "lang": "und-Mlym" },
+ { "lang": "und-Beng" },
+ { "lang": "und-Telu" },
+ { "lang": "und-Knda" },
+ { "lang": "und-Orya" },
+ { "lang": "und-Sinh" },
+ { "lang": "und-Khmr" },
+ { "lang": "und-Laoo" },
+ { "lang": "und-Mymr" },
+ { "lang": "und-Thaa" },
+ { "lang": "und-Cham" },
+ { "lang": "und-Ahom" },
+ { "lang": "und-Adlm" },
+ { "lang": "und-Avst" },
+ { "lang": "und-Bali" },
+ { "lang": "und-Bamu" },
+ { "lang": "und-Batk" },
+ { "lang": "und-Brah" },
+ { "lang": "und-Bugi" },
+ { "lang": "und-Buhd" },
+ { "lang": "und-Cans" },
+ { "lang": "und-Cari" },
+ { "lang": "und-Cakm" },
+ { "lang": "und-Cher" },
+ { "lang": "und-Copt" },
+ { "lang": "und-Xsux" },
+ { "lang": "und-Cprt" },
+ { "lang": "und-Dsrt" },
+ { "lang": "und-Egyp" },
+ { "lang": "und-Elba" },
+ { "lang": "und-Glag" },
+ { "lang": "und-Goth" },
+ { "lang": "und-Hano" },
+ { "lang": "und-Armi" },
+ { "lang": "und-Phli" },
+ { "lang": "und-Prti" },
+ { "lang": "und-Java" },
+ { "lang": "und-Kthi" },
+ { "lang": "und-Kali" },
+ { "lang": "und-Khar" },
+ { "lang": "und-Lepc" },
+ { "lang": "und-Limb" },
+ { "lang": "und-Linb" },
+ { "lang": "und-Lisu" },
+ { "lang": "und-Lyci" },
+ { "lang": "und-Lydi" },
+ { "lang": "und-Mand" },
+ { "lang": "und-Mtei" },
+ { "lang": "und-Talu" },
+ { "lang": "und-Nkoo" },
+ { "lang": "und-Ogam" },
+ { "lang": "und-Olck" },
+ { "lang": "und-Ital" },
+ { "lang": "und-Xpeo" },
+ { "lang": "und-Sarb" },
+ { "lang": "und-Orkh" },
+ { "lang": "und-Osge" },
+ { "lang": "und-Osma" },
+ { "lang": "und-Phnx" },
+ { "lang": "und-Rjng" },
+ { "lang": "und-Runr" },
+ { "lang": "und-Samr" },
+ { "lang": "und-Saur" },
+ { "lang": "und-Shaw" },
+ { "lang": "und-Sund" },
+ { "lang": "und-Sylo" },
+ { "lang": "und-Syre" },
+ { "lang": "und-Syrn" },
+ { "lang": "und-Syrj" },
+ { "lang": "und-Tglg" },
+ { "lang": "und-Tagb" },
+ { "lang": "und-Lana" },
+ { "lang": "und-Tavt" },
+ { "lang": "und-Tibt" },
+ { "lang": "und-Tfng" },
+ { "lang": "und-Ugar" },
+ { "lang": "und-Vaii" },
+ // NotoSansSymbol-Regular-Subsetted doesn't have any language but should be
+ // placed before the CJK fonts for reproducing the same fallback order.
+ { "id": "NotoSansSymbols-Regular-Subsetted" },
+ { "lang": "zh-Hans" },
+ { "lang": "zh-Hant,zh-Bopo" },
+ { "lang": "ja" },
+ { "lang": "ko" },
+ { "lang": "und-Zsye" },
+ { "lang": "und-Zsym" },
+ { "lang": "und-Tale" },
+ { "lang": "und-Yiii" },
+ { "lang": "und-Mong" },
+ { "lang": "und-Phag" },
+ { "lang": "und-Hluw" },
+ { "lang": "und-Bass" },
+ { "lang": "und-Bhks" },
+ { "lang": "und-Hatr" },
+ { "lang": "und-Lina" },
+ { "lang": "und-Mani" },
+ { "lang": "und-Marc" },
+ { "lang": "und-Merc" },
+ { "lang": "und-Plrd" },
+ { "lang": "und-Mroo" },
+ { "lang": "und-Mult" },
+ { "lang": "und-Nbat" },
+ { "lang": "und-Newa" },
+ { "lang": "und-Narb" },
+ { "lang": "und-Perm" },
+ { "lang": "und-Hmng" },
+ { "lang": "und-Palm" },
+ { "lang": "und-Pauc" },
+ { "lang": "und-Shrd" },
+ { "lang": "und-Sora" },
+ { "lang": "und-Gong" },
+ { "lang": "und-Rohg" },
+ { "lang": "und-Khoj" },
+ { "lang": "und-Gonm" },
+ { "lang": "und-Wcho" },
+ { "lang": "und-Wara" },
+ { "lang": "und-Gran" },
+ { "lang": "und-Modi" },
+ { "lang": "und-Dogr" },
+ { "lang": "und-Medf" },
+ { "lang": "und-Soyo" },
+ { "lang": "und-Takr" },
+ { "lang": "und-Hmnp" },
+ { "lang": "und-Yezi" }
+]
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1a3aa8eec5ec..b338a2ae2b79 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -46,9 +46,6 @@ filegroup {
srcs: [
"src/com/android/wm/shell/common/bubbles/*.kt",
"src/com/android/wm/shell/common/bubbles/*.java",
- "src/com/android/wm/shell/common/desktopmode/*.kt",
- "src/com/android/wm/shell/pip/PipContentOverlay.java",
- "src/com/android/wm/shell/util/**/*.java",
],
path: "src",
}
@@ -208,6 +205,7 @@ android_library {
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
":wm_shell-aidls",
+ ":wm_shell-shared-aidls",
],
resource_dirs: [
"res",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
index 15797cdb9aba..e21bf8fb723c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
parcelable GroupedRecentTaskInfo; \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
index a2d2b9aff597..65e079ef4f72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -25,6 +25,8 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.wm.shell.shared.split.SplitBounds;
+
import java.util.Arrays;
import java.util.List;
import java.util.Set;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
index c968e809bf61..f7ddf71245e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package com.android.wm.shell.shared.desktopmode;
parcelable DesktopModeTransitionSource; \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
index dbbf178613b5..d15fbed409b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode
+package com.android.wm.shell.shared.desktopmode
import android.os.Parcel
import android.os.Parcelable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index ff2d46e11107..cf39415b3fe6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.shared.pip;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
index 88b752822a20..7c1faa667d9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared.split;
import android.graphics.Rect;
import android.os.Parcel;
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 d7da0515f228..27194b344780 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
@@ -1066,14 +1066,30 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return true;
}
+ private void kickStartAnimation() {
+ startSystemAnimation();
+
+ // Dispatch the first progress after animation start for
+ // smoothing the initial animation, instead of waiting for next
+ // onMove.
+ final BackMotionEvent backFinish = mCurrentTracker
+ .createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
+ if (!mBackGestureStarted) {
+ // if the down -> up gesture happened before animation
+ // start, we have to trigger the uninterruptible transition
+ // to finish the back animation.
+ startPostCommitAnimation();
+ }
+ }
+
private void createAdapter() {
IBackAnimationRunner runner =
new IBackAnimationRunner.Stub() {
@Override
public void onAnimationStart(
RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
+ IBinder token,
IBackAnimationFinishedCallback finishedCallback) {
mShellExecutor.execute(
() -> {
@@ -1085,21 +1101,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
mBackAnimationFinishedCallback = finishedCallback;
mApps = apps;
- startSystemAnimation();
- mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
-
- // Dispatch the first progress after animation start for
- // smoothing the initial animation, instead of waiting for next
- // onMove.
- final BackMotionEvent backFinish = mCurrentTracker
- .createProgressEvent();
- dispatchOnBackProgressed(mActiveCallback, backFinish);
- if (!mBackGestureStarted) {
- // if the down -> up gesture happened before animation
- // start, we have to trigger the uninterruptible transition
- // to finish the back animation.
- startPostCommitAnimation();
+ // app only visible after transition ready, break for now.
+ if (token != null) {
+ return;
}
+ kickStartAnimation();
+ mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
});
}
@@ -1199,6 +1206,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull SurfaceControl.Transaction st,
@NonNull SurfaceControl.Transaction ft,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+ kickStartAnimation();
+ }
// Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
// need to post to ShellExecutor when called.
if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
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 3dc33c264044..b508c1ba7fe4 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
@@ -1225,7 +1225,7 @@ public class BubbleController implements ConfigurationChangeListener,
mBubblePositioner.setBubbleBarLocation(location);
mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
if (mBubbleData.getSelectedBubble() != null) {
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+ showExpandedViewForBubbleBar();
}
}
@@ -1243,7 +1243,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
// We did not remove the selected bubble. Expand it again
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+ showExpandedViewForBubbleBar();
}
}
@@ -1997,15 +1997,10 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void expansionChanged(boolean isExpanded) {
- if (mLayerView != null) {
- if (!isExpanded) {
- mLayerView.collapse();
- } else {
- BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
- if (selectedBubble != null) {
- mLayerView.showExpandedView(selectedBubble);
- }
- }
+ // in bubble bar mode, let the request to show the expanded view come from launcher.
+ // only collapse here if we're collapsing.
+ if (mLayerView != null && !isExpanded) {
+ mLayerView.collapse();
}
}
@@ -2151,6 +2146,13 @@ public class BubbleController implements ConfigurationChangeListener,
}
};
+ private void showExpandedViewForBubbleBar() {
+ BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+ if (selectedBubble != null && mLayerView != null) {
+ mLayerView.showExpandedView(selectedBubble);
+ }
+ }
+
private void updateOverflowButtonDot() {
BubbleOverflow overflow = mBubbleData.getOverflow();
if (overflow == null) return;
@@ -2532,6 +2534,15 @@ public class BubbleController implements ConfigurationChangeListener,
if (mLayerView != null) mLayerView.updateExpandedView();
});
}
+
+ @Override
+ public void showExpandedView() {
+ mMainExecutor.execute(() -> {
+ if (mLayerView != null) {
+ showExpandedViewForBubbleBar();
+ }
+ });
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5c789749412c..5779a8f7bcc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -53,4 +53,6 @@ interface IBubbles {
oneway void showShortcutBubble(in ShortcutInfo info) = 12;
oneway void showAppBubble(in Intent intent) = 13;
+
+ oneway void showExpandedView() = 14;
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index c2ee223b916a..972b78f6ca9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -39,6 +39,7 @@ import android.view.InsetsState;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener;
@@ -67,6 +68,7 @@ import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.IntPredicate;
import java.util.function.Predicate;
/**
@@ -189,6 +191,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
@NonNull
private final CompatUIStatusManager mCompatUIStatusManager;
+ @NonNull
+ private final IntPredicate mInDesktopModePredicate;
+
public CompatUIController(@NonNull Context context,
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -202,7 +207,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
@NonNull CompatUIConfiguration compatUIConfiguration,
@NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
@NonNull AccessibilityManager accessibilityManager,
- @NonNull CompatUIStatusManager compatUIStatusManager) {
+ @NonNull CompatUIStatusManager compatUIStatusManager,
+ @NonNull IntPredicate isDesktopModeEnablePredicate) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -218,6 +224,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
DISAPPEAR_DELAY_MS, flags);
mCompatUIStatusManager = compatUIStatusManager;
+ mInDesktopModePredicate = isDesktopModeEnablePredicate;
shellInit.addInitCallback(this::onInit, this);
}
@@ -251,7 +258,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
updateActiveTaskInfo(taskInfo);
}
- if (taskInfo.configuration == null || taskListener == null) {
+ // We close all the Compat UI educations in case we're in desktop mode.
+ if (taskInfo.configuration == null || taskListener == null
+ || isInDesktopMode(taskInfo.displayId)) {
// Null token means the current foreground activity is not in compatibility mode.
removeLayouts(taskInfo.taskId);
return;
@@ -350,7 +359,6 @@ public class CompatUIController implements OnDisplaysChangedListener,
mOnInsetsChangedListeners.remove(displayId);
}
-
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
updateDisplayLayout(displayId);
@@ -692,7 +700,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
mContext.startActivityAsUser(intent, userHandle);
}
- private void removeLayouts(int taskId) {
+ @VisibleForTesting
+ void removeLayouts(int taskId) {
final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId);
if (compatLayout != null) {
compatLayout.release();
@@ -825,4 +834,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
boolean mHasShownCameraCompatHint;
boolean mHasShownUserAspectRatioSettingsButtonHint;
}
+
+ private boolean isInDesktopMode(int displayId) {
+ return Flags.skipCompatUiEducationInDesktopMode()
+ && mInDesktopModePredicate.test(displayId);
+ }
}
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 98536bf98f0b..42937c134e7f 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
@@ -137,6 +137,7 @@ import dagger.Module;
import dagger.Provides;
import java.util.Optional;
+import java.util.function.IntPredicate;
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
@@ -261,6 +262,7 @@ public abstract class WMShellBaseModule {
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
Lazy<AccessibilityManager> accessibilityManager,
CompatUIRepository compatUIRepository,
+ Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
@NonNull CompatUIState compatUIState,
@NonNull CompatUIComponentIdGenerator componentIdGenerator,
@NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -273,6 +275,10 @@ public abstract class WMShellBaseModule {
new DefaultCompatUIHandler(compatUIRepository, compatUIState,
componentIdGenerator, compatUIComponentFactory, mainExecutor));
}
+ final IntPredicate inDesktopModePredicate =
+ desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
+ modeTaskRepository.getVisibleTaskCount(displayId) > 0)
+ .orElseGet(() -> displayId -> false);
return Optional.of(
new CompatUIController(
context,
@@ -288,7 +294,8 @@ public abstract class WMShellBaseModule {
compatUIConfiguration.get(),
compatUIShellCommandHandler.get(),
accessibilityManager.get(),
- compatUIStatusManager));
+ compatUIStatusManager,
+ inDesktopModePredicate));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 51ce2c6707ac..3464fef07f33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -42,6 +42,7 @@ import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
+import com.android.wm.shell.pip2.phone.PipTaskListener;
import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.pip2.phone.PipTransitionState;
@@ -73,12 +74,13 @@ public abstract class Pip2Module {
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
PipTouchHandler pipTouchHandler,
+ PipTaskListener pipTaskListener,
@NonNull PipScheduler pipScheduler,
@NonNull PipTransitionState pipStackListenerController,
@NonNull PipUiStateChangeController pipUiStateChangeController) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
- pipStackListenerController, pipUiStateChangeController);
+ pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
+ pipScheduler, pipStackListenerController, pipUiStateChangeController);
}
@WMSingleton
@@ -123,9 +125,11 @@ public abstract class Pip2Module {
@Provides
static PipScheduler providePipScheduler(Context context,
PipBoundsState pipBoundsState,
+ PhonePipMenuController pipMenuController,
@ShellMainThread ShellExecutor mainExecutor,
PipTransitionState pipTransitionState) {
- return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
+ return new PipScheduler(context, pipBoundsState, pipMenuController,
+ mainExecutor, pipTransitionState);
}
@WMSingleton
@@ -190,4 +194,17 @@ public abstract class Pip2Module {
PipTransitionState pipTransitionState) {
return new PipUiStateChangeController(pipTransitionState);
}
+
+ @WMSingleton
+ @Provides
+ static PipTaskListener providePipTaskListener(Context context,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
+ PipScheduler pipScheduler,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipTaskListener(context, shellTaskOrganizer, pipTransitionState,
+ pipScheduler, pipBoundsState, pipBoundsAlgorithm, mainExecutor);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 31c8f1e45007..cca750014fc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,8 +18,8 @@ package com.android.wm.shell.desktopmode;
import android.graphics.Region;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index eca3c1fdc65a..dba8c9367654 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.desktopmode
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.sysui.ShellCommandHandler
import java.io.PrintWriter
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
index b24bd10eaa0d..d6fccd116061 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
@@ -17,7 +17,7 @@
package com.android.wm.shell.desktopmode
import android.view.WindowManager.TransitionType
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TYPES
/**
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 ffd534b6b814..2852631656b5 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
@@ -69,7 +69,6 @@ import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
@@ -89,6 +88,7 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVI
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
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
import com.android.wm.shell.splitscreen.SplitScreenController
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 1a103d345ca7..d72ec90957fc 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
@@ -22,13 +22,15 @@ import android.graphics.Rect
import android.os.Bundle
import android.os.IBinder
import android.os.SystemClock
+import android.os.SystemProperties
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
-import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.InteractionJankMonitor
@@ -893,13 +895,10 @@ constructor(
) {
private val positionSpringConfig =
- PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW,
- SpringForce.DAMPING_RATIO_LOW_BOUNCY
- )
+ PhysicsAnimator.SpringConfig(POSITION_SPRING_STIFFNESS, POSITION_SPRING_DAMPING_RATIO)
private val sizeSpringConfig =
- PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ PhysicsAnimator.SpringConfig(SIZE_SPRING_STIFFNESS, SIZE_SPRING_DAMPING_RATIO)
/**
* @return layers in order:
@@ -929,7 +928,7 @@ constructor(
finishTransaction.hide(homeLeash)
// Setup freeform tasks before animation
state.freeformTaskChanges.forEach { change ->
- val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+ val startScale = FREEFORM_TASKS_INITIAL_SCALE
val startX =
change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2
val startY =
@@ -994,9 +993,22 @@ constructor(
(animBounds.width() - startBounds.width()).toFloat() /
(endBounds.width() - startBounds.width())
val animScale = startScale + animFraction * (1 - startScale)
- // Freeform animation starts 50% in the animation
- val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f
- val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+ // Freeform animation starts with freeform animation offset relative to the commit
+ // animation and plays until the commit animation ends. For instance:
+ // - if the freeform animation offset is `0.0` the freeform tasks animate alongside
+ // - if the freeform animation offset is `0.6` the freeform tasks will
+ // start animating at 60% fraction of the commit animation and will complete when
+ // the commit animation fraction is 100%.
+ // - if the freeform animation offset is `1.0` then freeform tasks will appear
+ // without animation after commit animation finishes.
+ val freeformAnimFraction =
+ if (FREEFORM_TASKS_ANIM_OFFSET != 1f) {
+ max(animFraction - FREEFORM_TASKS_ANIM_OFFSET, 0f) /
+ (1f - FREEFORM_TASKS_ANIM_OFFSET)
+ } else {
+ 0f
+ }
+ val freeformStartScale = FREEFORM_TASKS_INITIAL_SCALE
val freeformAnimScale =
freeformStartScale + freeformAnimFraction * (1 - freeformStartScale)
tx.apply {
@@ -1032,10 +1044,53 @@ constructor(
}
companion object {
+ /** The freeform tasks initial scale when committing the drag-to-desktop gesture. */
+ private val FREEFORM_TASKS_INITIAL_SCALE =
+ propertyValue("freeform_tasks_initial_scale", scale = 100f, default = 0.9f)
+
+ /** The freeform tasks animation offset relative to the whole animation duration. */
+ private val FREEFORM_TASKS_ANIM_OFFSET =
+ propertyValue("freeform_tasks_anim_offset", scale = 100f, default = 0.5f)
+
+ /** The spring force stiffness used to place the window into the final position. */
+ private val POSITION_SPRING_STIFFNESS =
+ propertyValue("position_stiffness", default = SpringForce.STIFFNESS_LOW)
+
+ /** The spring force damping ratio used to place the window into the final position. */
+ private val POSITION_SPRING_DAMPING_RATIO =
+ propertyValue(
+ "position_damping_ratio",
+ scale = 100f,
+ default = SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ )
+
+ /** The spring force stiffness used to resize the window into the final bounds. */
+ private val SIZE_SPRING_STIFFNESS =
+ propertyValue("size_stiffness", default = SpringForce.STIFFNESS_LOW)
+
+ /** The spring force damping ratio used to resize the window into the final bounds. */
+ private val SIZE_SPRING_DAMPING_RATIO =
+ propertyValue(
+ "size_damping_ratio",
+ scale = 100f,
+ default = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ )
+
+ /** Drag to desktop transition system properties group. */
+ @VisibleForTesting
+ const val SYSTEM_PROPERTIES_GROUP = "persist.wm.debug.desktop_transitions.drag_to_desktop"
+
/**
- * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop
- * gesture.
+ * Drag to desktop transition system property value with [name].
+ *
+ * @param scale an optional scale to apply to the value read from the system property.
+ * @param default a default value to return if the system property isn't set.
*/
- private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f
+ @VisibleForTesting
+ fun propertyValue(name: String, scale: Float = 1f, default: Float = 0f): Float =
+ SystemProperties.getInt(
+ /* key= */ "$SYSTEM_PROPERTIES_GROUP.$name",
+ /* def= */ (default * scale).toInt()
+ ) / scale
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 04506c1e66f2..80e106f3990b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -41,7 +41,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 171378f9a164..e87be520cc91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -44,7 +44,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
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 a7ec2037706d..b036e40e6e16 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
@@ -18,8 +18,8 @@ package com.android.wm.shell.desktopmode;
import android.app.ActivityManager.RunningTaskInfo;
import android.window.RemoteTransition;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
/**
* Interface that is exposed to remote callers to manipulate desktop mode features.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 84f6af4125b8..72d1a76b17e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -27,10 +27,13 @@ building to check the log state (is enabled) before printing the print format st
traces in Winscope)
### Kotlin
+Kotlin protologging is supported but not as optimized as in Java.
-Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
-class which has a similar API to the Java ProtoLog class.
+The Protolog tool does not yet have support for Kotlin code ([b/168581922](https://b.corp.google.com/issues/168581922)).
+
+What this implies is that ProtoLogs are not pre-processed to extract the static strings out when used in Kotlin. So,
+there is no memory gain when using ProtoLogging in Kotlin. The logs will still be traced to Perfetto, but with a subtly
+worse performance due to the additional string interning that needs to be done at run time instead of at build time.
### Enabling ProtoLog command line logging
Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index b0c896fbe516..4df649ca8c93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -43,6 +43,7 @@ import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.transition.Transitions;
import java.lang.annotation.Retention;
@@ -418,7 +419,7 @@ public class PipAnimationController {
}
SurfaceControl getContentOverlayLeash() {
- return mContentOverlay == null ? null : mContentOverlay.mLeash;
+ return mContentOverlay == null ? null : mContentOverlay.getLeash();
}
void setColorContentOverlay(Context context) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 2de545a829ad..e4cd10f37d37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -91,6 +91,7 @@ import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -361,8 +362,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
SurfaceControl mPipOverlay;
/**
- * The app bounds used for the buffer size of the
- * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+ * The app bounds used for the buffer size of the {@link PipContentOverlay.PipAppIconOverlay}.
*
* Note that this is empty if the overlay is removed or if it's some other type of overlay
* defined in {@link PipContentOverlay}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index b102e40147e1..05d19846bfee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -75,6 +75,7 @@ import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 88f9e4c740e3..d565776c9917 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -134,9 +134,10 @@ public class PipResizeAnimator extends ValueAnimator
Rect baseBounds, Rect targetBounds, float degrees) {
Matrix transformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
- final float scale = (float) targetBounds.width() / baseBounds.width();
+ final float scaleX = (float) targetBounds.width() / baseBounds.width();
+ final float scaleY = (float) targetBounds.height() / baseBounds.height();
- transformTensor.setScale(scale, scale);
+ transformTensor.setScale(scaleX, scaleY);
transformTensor.postTranslate(targetBounds.left, targetBounds.top);
transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 218d456e9596..0324fdba0fbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -56,7 +56,6 @@ import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import java.util.Optional;
-import java.util.function.Consumer;
/**
* A helper to animate and manipulate the PiP.
@@ -134,18 +133,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
- @Nullable private Runnable mUpdateMovementBoundsRunnable;
-
- private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
- if (mPipBoundsState.getBounds().equals(newBounds)) {
- return;
- }
-
- mMenuController.updateMenuLayout(newBounds);
- mPipBoundsState.setBounds(newBounds);
- maybeUpdateMovementBounds();
- };
-
/**
* Whether we're springing to the touch event location (vs. moving it to that position
* instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
@@ -683,16 +670,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
cleanUpHighPerfSessionMaybe();
}
- void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
- mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
- }
-
- private void maybeUpdateMovementBounds() {
- if (mUpdateMovementBoundsRunnable != null) {
- mUpdateMovementBoundsRunnable.run();
- }
- }
-
/**
* Notifies the floating coordinator that we're moving, and sets the animating to bounds so
* we return these bounds from
@@ -720,7 +697,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/**
* Directly resizes the PiP to the given {@param bounds}.
*/
- private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+ void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
// Do not carry out any resizing if we are dragging or physics animator is running.
return;
@@ -813,7 +790,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
cleanUpHighPerfSessionMaybe();
// Signal that the transition is done - should update transition state by default.
- mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(destinationBounds, false /* configAtEnd */);
}
private void startResizeAnimation(SurfaceControl.Transaction startTx,
@@ -829,8 +806,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
destinationBounds, duration, 0f /* angle */);
animator.setAnimationEndCallback(() -> {
- mUpdateBoundsCallback.accept(destinationBounds);
-
// In case an ongoing drag/fling was present before a deterministic resize transition
// kicked in, we need to update the update bounds properly before cleaning in-motion
// state.
@@ -839,7 +814,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
cleanUpHighPerfSessionMaybe();
// Signal that we are done with resize transition
- mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(destinationBounds, true /* configAtEnd */);
});
animator.start();
}
@@ -849,7 +824,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
// The physics animation ended, though we may not necessarily be done animating, such as
// when we're still dragging after moving out of the magnetic target. Only set the final
// bounds state and clear motion bounds completely if the whole animation is over.
- mPipBoundsState.setBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
}
mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index d28204add0ac..f5ef64dff94b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -50,7 +50,6 @@ import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import java.io.PrintWriter;
-import java.util.function.Consumer;
/**
* Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -86,8 +85,6 @@ public class PipResizeGestureHandler implements
private final Rect mUserResizeBounds = new Rect();
private final Rect mDownBounds = new Rect();
private final Rect mStartBoundsAfterRelease = new Rect();
- private final Runnable mUpdateMovementBoundsRunnable;
- private final Consumer<Rect> mUpdateResizeBoundsCallback;
private float mTouchSlop;
@@ -121,7 +118,6 @@ public class PipResizeGestureHandler implements
PipTouchState pipTouchState,
PipScheduler pipScheduler,
PipTransitionState pipTransitionState,
- Runnable updateMovementBoundsRunnable,
PipUiEventLogger pipUiEventLogger,
PhonePipMenuController menuActivityController,
ShellExecutor mainExecutor,
@@ -138,18 +134,9 @@ public class PipResizeGestureHandler implements
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
- mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
-
- mUpdateResizeBoundsCallback = (rect) -> {
- mUserResizeBounds.set(rect);
- // mMotionHelper.synchronizePinnedStackBounds();
- mPipBoundsState.setBounds(rect);
- mUpdateMovementBoundsRunnable.run();
- resetState();
- };
}
void init() {
@@ -563,11 +550,13 @@ public class PipResizeGestureHandler implements
mLastResizeBounds, duration, mAngle);
animator.setAnimationEndCallback(() -> {
// All motion operations have actually finished, so make bounds cache updates.
- mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
+ mUserResizeBounds.set(mLastResizeBounds);
+ resetState();
cleanUpHighPerfSessionMaybe();
// Signal that we are done with resize transition
- mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(
+ mLastResizeBounds, true /* configAtEnd */);
});
animator.start();
break;
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 ac670cf3f828..f4defdc7963c 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
@@ -52,11 +52,14 @@ public class PipScheduler {
private final Context mContext;
private final PipBoundsState mPipBoundsState;
+ private final PhonePipMenuController mPipMenuController;
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
private PipSchedulerReceiver mSchedulerReceiver;
private PipTransitionController mPipTransitionController;
+ @Nullable private Runnable mUpdateMovementBoundsRunnable;
+
/**
* Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
* This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -94,10 +97,12 @@ public class PipScheduler {
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
+ PhonePipMenuController pipMenuController,
ShellExecutor mainExecutor,
PipTransitionState pipTransitionState) {
mContext = context;
mPipBoundsState = pipBoundsState;
+ mPipMenuController = pipMenuController;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
@@ -189,9 +194,13 @@ public class PipScheduler {
* Signals to Core to finish the PiP resize transition.
* Note that we do not allow any actual WM Core changes at this point.
*
+ * @param toBounds destination bounds used only for internal state updates - not sent to Core.
* @param configAtEnd true if we are waiting for config updates at the end of the transition.
*/
- public void scheduleFinishResizePip(boolean configAtEnd) {
+ public void scheduleFinishResizePip(Rect toBounds, boolean configAtEnd) {
+ // Make updates to the internal state to reflect new bounds
+ onFinishingPipResize(toBounds);
+
SurfaceControl.Transaction tx = null;
if (configAtEnd) {
tx = new SurfaceControl.Transaction();
@@ -238,4 +247,23 @@ public class PipScheduler {
tx.setMatrix(leash, transformTensor, mMatrixTmp);
tx.apply();
}
+
+ void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
+ mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+ }
+
+ private void maybeUpdateMovementBounds() {
+ if (mUpdateMovementBoundsRunnable != null) {
+ mUpdateMovementBoundsRunnable.run();
+ }
+ }
+
+ private void onFinishingPipResize(Rect newBounds) {
+ if (mPipBoundsState.getBounds().equals(newBounds)) {
+ return;
+ }
+ mPipBoundsState.setBounds(newBounds);
+ mPipMenuController.updateMenuLayout(newBounds);
+ maybeUpdateMovementBounds();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
new file mode 100644
index 000000000000..7f168800fb29
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
+
+import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.SurfaceControl;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+
+/**
+ * A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via
+ * Transitions framework directly.
+ * Hence, it's the intention to keep the usage of this class for a very limited set of cases.
+ */
+public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
+ PipTransitionState.PipTransitionStateChangedListener {
+ private static final int ASPECT_RATIO_CHANGE_DURATION = 250;
+ private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
+
+ private final Context mContext;
+ private final PipTransitionState mPipTransitionState;
+ private final PipScheduler mPipScheduler;
+ private final PipBoundsState mPipBoundsState;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final ShellExecutor mMainExecutor;
+ private final PictureInPictureParams mPictureInPictureParams =
+ new PictureInPictureParams.Builder().build();
+
+ private boolean mWaitingForAspectRatioChange = false;
+
+ public PipTaskListener(Context context,
+ ShellTaskOrganizer shellTaskOrganizer,
+ PipTransitionState pipTransitionState,
+ PipScheduler pipScheduler,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mPipTransitionState = pipTransitionState;
+ mPipScheduler = pipScheduler;
+ mPipBoundsState = pipBoundsState;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mMainExecutor = mainExecutor;
+
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ mMainExecutor.execute(() -> {
+ shellTaskOrganizer.addListenerForType(this,
+ ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP);
+ });
+ }
+ }
+
+ void setPictureInPictureParams(@Nullable PictureInPictureParams params) {
+ if (mPictureInPictureParams.equals(params)) {
+ return;
+ }
+ mPictureInPictureParams.copyOnlySet(params != null ? params
+ : new PictureInPictureParams.Builder().build());
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ PictureInPictureParams params = taskInfo.pictureInPictureParams;
+ if (mPictureInPictureParams.equals(params)) {
+ return;
+ }
+ setPictureInPictureParams(params);
+ float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
+ if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
+ mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+ onAspectRatioChanged(newAspectRatio);
+ });
+ }
+ }
+
+ private void onAspectRatioChanged(float ratio) {
+ mPipBoundsState.setAspectRatio(ratio);
+
+ final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds(
+ mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio());
+ // Avoid scheduling a resize transition if destination bounds are unchanged, otherise
+ // we could end up with a no-op transition.
+ if (!destinationBounds.equals(mPipBoundsState.getBounds())) {
+ Bundle extra = new Bundle();
+ extra.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+ }
+ }
+
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ switch (newState) {
+ case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+ mWaitingForAspectRatioChange = extra.getBoolean(ANIMATING_ASPECT_RATIO_CHANGE);
+ if (!mWaitingForAspectRatioChange) break;
+
+ mPipScheduler.scheduleAnimateResizePip(
+ mPipBoundsAlgorithm.getAdjustedDestinationBounds(
+ mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio()),
+ false /* configAtEnd */, ASPECT_RATIO_CHANGE_DURATION);
+ break;
+ case PipTransitionState.CHANGING_PIP_BOUNDS:
+ final SurfaceControl.Transaction startTx = extra.getParcelable(
+ PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishTx = extra.getParcelable(
+ PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
+ final Rect destinationBounds = extra.getParcelable(
+ PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
+ final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+
+ Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash,
+ "Leash is null for bounds transition.");
+
+ if (mWaitingForAspectRatioChange) {
+ PipResizeAnimator animator = new PipResizeAnimator(mContext,
+ mPipTransitionState.mPinnedTaskLeash, startTx, finishTx,
+ destinationBounds,
+ mPipBoundsState.getBounds(), destinationBounds, duration,
+ 0f /* delta */);
+ animator.setAnimationEndCallback(() -> {
+ mPipScheduler.scheduleFinishResizePip(
+ destinationBounds, false /* configAtEnd */);
+ });
+ animator.start();
+ }
+ break;
+ }
+ }
+}
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 d75fa00b1fdd..029f001401c5 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
@@ -206,7 +206,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mMenuController.addListener(new PipMenuListener());
mGesture = new DefaultPipTouchGesture();
mMotionHelper = pipMotionHelper;
- mMotionHelper.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
+ mPipScheduler.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
@@ -219,8 +219,8 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
menuController::hideMenu,
mainExecutor);
mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
- pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState,
- this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor,
+ pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
+ menuController, mainExecutor,
mPipPerfHintController);
mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index ed18712b283d..44baabdd5e2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -50,10 +50,10 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.pip.PipContentOverlay;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterExitAnimator;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -87,6 +87,7 @@ public class PipTransition extends PipTransitionController implements
//
private final Context mContext;
+ private final PipTaskListener mPipTaskListener;
private final PipScheduler mPipScheduler;
private final PipTransitionState mPipTransitionState;
@@ -118,6 +119,7 @@ public class PipTransition extends PipTransitionController implements
PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipTaskListener pipTaskListener,
PipScheduler pipScheduler,
PipTransitionState pipTransitionState,
PipUiStateChangeController pipUiStateChangeController) {
@@ -125,6 +127,7 @@ public class PipTransition extends PipTransitionController implements
pipBoundsAlgorithm);
mContext = context;
+ mPipTaskListener = pipTaskListener;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
mPipTransitionState = pipTransitionState;
@@ -510,6 +513,7 @@ public class PipTransition extends PipTransitionController implements
// cache the original task token to check for multi-activity case later
final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+ mPipTaskListener.setPictureInPictureParams(pipParams);
mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
pipParams, mPipBoundsAlgorithm);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 4048c5b8feab..ebfd3571ae6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@ import android.os.Bundle;
import android.view.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
/**
* Interface that is exposed to remote callers to fetch recent tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 77b8663861ab..8c5d1e7e069d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -19,8 +19,8 @@ package com.android.wm.shell.recents;
import android.annotation.Nullable;
import android.graphics.Color;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.util.List;
import java.util.concurrent.Executor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 2f0af8557538..39bea1bed447 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -51,16 +51,16 @@ import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 7a9eb1c582b7..c90da052dd72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -30,7 +30,7 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
-import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
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 0b5c75104f65..95f864a775be 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
@@ -133,13 +133,13 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.SplitBounds;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dalvik.annotation.optimization.NeverCompile;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index a9a4e1093392..4fc6c4489f2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -18,8 +18,8 @@ package com.android.wm.shell.transition;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
deleted file mode 100644
index 482aaab6bc74..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file KtProtolog.kt = file:platform/development:/tools/winscope/OWNERS
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 20a406f61ed3..1f95667f4e35 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
@@ -97,7 +97,6 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -107,6 +106,7 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
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 8e87d0ff33c6..2bec3fa6418d 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
@@ -26,7 +26,7 @@ import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
-import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
@@ -79,10 +79,10 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 114c33114421..deef37874e79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -318,7 +318,7 @@ class MaximizeMenu(
rootView.setOnTouchListener { _, event ->
if (event.actionMasked == ACTION_OUTSIDE) {
onOutsideTouchListener?.invoke()
- false
+ return@setOnTouchListener false
}
true
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 8584b599a96c..3fb67cd522c0 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -24,14 +24,19 @@ import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
+import android.tools.flicker.assertors.assertions.AppWindowMaintainsAspectRatioAlways
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
+import android.tools.flicker.assertors.assertions.AppWindowReturnsToStartBoundsAndPosition
import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
import android.tools.flicker.config.ScenarioId
-import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.config.desktopmode.Components.DESKTOP_MODE_APP
import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER
+import android.tools.flicker.config.desktopmode.Components.NON_RESIZABLE_APP
import android.tools.flicker.extractors.ITransitionMatcher
import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
import android.tools.flicker.extractors.TaggedCujTransitionMatcher
@@ -62,13 +67,11 @@ class DesktopModeFlickerScenarios {
assertions =
AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
- AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
- AppWindowHasDesktopModeInitialBoundsAtTheEnd(
- Components.DESKTOP_MODE_APP
- ),
- AppWindowBecomesVisible(DESKTOP_WALLPAPER)
- )
+ AppLayerIsVisibleAlways(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(DESKTOP_MODE_APP),
+ AppWindowBecomesVisible(DESKTOP_WALLPAPER)
+ )
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -97,11 +100,10 @@ class DesktopModeFlickerScenarios {
assertions =
AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- )
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+ AppLayerIsVisibleAtStart(DESKTOP_MODE_APP),
+ AppLayerIsInvisibleAtEnd(DESKTOP_MODE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val CLOSE_LAST_APP =
@@ -125,10 +127,10 @@ class DesktopModeFlickerScenarios {
assertions =
AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
- AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
- )
+ AppWindowIsInvisibleAtEnd(DESKTOP_MODE_APP),
+ LauncherWindowReplacesAppAsTopWindow(DESKTOP_MODE_APP),
+ AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
+ )
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -156,9 +158,28 @@ class DesktopModeFlickerScenarios {
)
.build(),
assertions =
- AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
- listOf(AppWindowHasSizeOfAtLeast(Components.DESKTOP_MODE_APP, 770, 700))
+ AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(AppWindowHasSizeOfAtLeast(DESKTOP_MODE_APP, 770, 700))
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+
+ val SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"),
+ extractor =
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ )
+ .build(),
+ assertions = listOf(
+ AppWindowIsVisibleAlways(NON_RESIZABLE_APP),
+ AppWindowOnTopAtEnd(NON_RESIZABLE_APP),
+ AppWindowRemainInsideDisplayBounds(NON_RESIZABLE_APP),
+ AppWindowMaintainsAspectRatioAlways(NON_RESIZABLE_APP),
+ AppWindowReturnsToStartBoundsAndPosition(NON_RESIZABLE_APP)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt
new file mode 100644
index 000000000000..582658f8cb11
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize non-resizable app window by dragging it to the left edge of the screen.
+ *
+ * Assert that the app window keeps the same size and returns to its original pre-drag position.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeNonResizableAppWindowLeftWithDrag :
+ SnapResizeAppWindowWithDrag(toLeft = true, isResizable = false) {
+ @ExpectedScenarios(["SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"])
+ @Test
+ override fun snapResizeAppWindowWithDrag() = super.snapResizeAppWindowWithDrag()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ .use(SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.kt
new file mode 100644
index 000000000000..7205ec412c99
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize non-resizable app window by dragging it to the right edge of the screen.
+ *
+ * Assert that the app window keeps the same size and returns to its original pre-drag position.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeNonResizableAppWindowRightWithDrag :
+ SnapResizeAppWindowWithDrag(toLeft = false, isResizable = false) {
+ @ExpectedScenarios(["SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"])
+ @Test
+ override fun snapResizeAppWindowWithDrag() = super.snapResizeAppWindowWithDrag()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ .use(SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 90e3f7fdb973..1e4b8b62a082 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -881,7 +881,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
if (mController.mBackAnimationAdapter != null) {
mController.mBackAnimationAdapter.getRunner().onAnimationStart(
- targets, null, null, mBackAnimationFinishedCallback);
+ targets, null /* prepareOpenTransition */, mBackAnimationFinishedCallback);
mShellExecutor.flushAll();
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index b39cf19a155a..d5287e742c2c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -35,9 +35,12 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
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.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -90,6 +93,9 @@ public class CompatUIControllerTest extends ShellTestCase {
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private CompatUIController mController;
private ShellInit mShellInit;
@Mock
@@ -122,7 +128,6 @@ public class CompatUIControllerTest extends ShellTestCase {
private CompatUIConfiguration mCompatUIConfiguration;
@Mock
private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
-
@Mock
private AccessibilityManager mAccessibilityManager;
@@ -132,6 +137,8 @@ public class CompatUIControllerTest extends ShellTestCase {
@NonNull
private CompatUIStatusManager mCompatUIStatusManager;
+ private boolean mInDesktopModePredicateResult;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -157,7 +164,7 @@ public class CompatUIControllerTest extends ShellTestCase {
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
- mCompatUIStatusManager) {
+ mCompatUIStatusManager, i -> mInDesktopModePredicateResult) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -685,6 +692,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(false);
@@ -695,6 +703,34 @@ public class CompatUIControllerTest extends ShellTestCase {
eq(mMockTaskListener));
}
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+ public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() {
+ mInDesktopModePredicateResult = false;
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+
+ mInDesktopModePredicateResult = true;
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController).removeLayouts(taskInfo.taskId);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+ public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() {
+ mInDesktopModePredicateResult = false;
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+
+ mInDesktopModePredicateResult = true;
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+ }
+
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
/* isFocused */ false, /* isTopActivityTransparent */ false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
index 518c00d377ad..db4e93de9541 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
@@ -18,11 +18,6 @@ package com.android.wm.shell.desktopmode
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.TASK_DRAG
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
@@ -33,6 +28,11 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getExitTransitionType
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
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 058a26a06f59..d2487209dcef 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
@@ -83,7 +83,6 @@ import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
@@ -94,6 +93,7 @@ import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
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 16a234b9e2f2..5b028371be2b 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
@@ -8,6 +8,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WindowingMode
import android.graphics.PointF
import android.os.IBinder
+import android.os.SystemProperties
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
@@ -16,6 +17,7 @@ import android.window.TransitionInfo
import android.window.TransitionInfo.FLAG_IS_WALLPAPER
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
@@ -29,19 +31,24 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import java.util.function.Supplier
+import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.MockitoSession
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.whenever
+import org.mockito.quality.Strictness
/** Tests of [DragToDesktopTransitionHandler]. */
@SmallTest
@@ -61,10 +68,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
private lateinit var defaultHandler: DragToDesktopTransitionHandler
private lateinit var springHandler: SpringDragToDesktopTransitionHandler
+ private lateinit var mockitoSession: MockitoSession
@Before
fun setUp() {
- defaultHandler = DefaultDragToDesktopTransitionHandler(
+ defaultHandler =
+ DefaultDragToDesktopTransitionHandler(
context,
transitions,
taskDisplayAreaOrganizer,
@@ -72,7 +81,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
transactionSupplier,
)
.apply { setSplitScreenController(splitScreenController) }
- springHandler = SpringDragToDesktopTransitionHandler(
+ springHandler =
+ SpringDragToDesktopTransitionHandler(
context,
transitions,
taskDisplayAreaOrganizer,
@@ -80,6 +90,16 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
transactionSupplier,
)
.apply { setSplitScreenController(splitScreenController) }
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(SystemProperties::class.java)
+ .startMocking()
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
}
@Test
@@ -357,6 +377,77 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
verify(finishCallback).onTransitionFinished(null)
}
+ @Test
+ fun propertyValue_returnsSystemPropertyValue() {
+ val name = "property_name"
+ val value = 10f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt()))
+ .thenReturn(value.toInt())
+
+ assertEquals(
+ "Expects to return system properties stored value",
+ /* expected= */ value,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name)
+ )
+ }
+
+ @Test
+ fun propertyValue_withScale_returnsScaledSystemPropertyValue() {
+ val name = "property_name"
+ val value = 10f
+ val scale = 100f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt()))
+ .thenReturn(value.toInt())
+
+ assertEquals(
+ "Expects to return scaled system properties stored value",
+ /* expected= */ value / scale,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale)
+ )
+ }
+
+ @Test
+ fun propertyValue_notSet_returnsDefaultValue() {
+ val name = "property_name"
+ val defaultValue = 50f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(defaultValue.toInt())))
+ .thenReturn(defaultValue.toInt())
+
+ assertEquals(
+ "Expects to return the default value",
+ /* expected= */ defaultValue,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
+ name,
+ default = defaultValue
+ )
+ )
+ }
+
+ @Test
+ fun propertyValue_withScaleNotSet_returnsDefaultValue() {
+ val name = "property_name"
+ val defaultValue = 0.5f
+ val scale = 100f
+ // Default value is multiplied when provided as a default value for [SystemProperties]
+ val scaledDefault = (defaultValue * scale).toInt()
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(scaledDefault)))
+ .thenReturn(scaledDefault)
+
+ assertEquals(
+ "Expects to return the default value",
+ /* expected= */ defaultValue,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
+ name,
+ default = defaultValue,
+ scale = scale
+ )
+ )
+ }
+
private fun startDrag(
handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo = createTask(),
@@ -462,4 +553,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
}
}
+
+ private fun systemPropertiesKey(name: String) =
+ "${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index e5157c974e2d..e0463b41ad20 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -48,7 +48,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index 6736593bba5b..0c3f98a324cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -24,13 +24,13 @@ import android.window.IWindowContainerToken
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SINGLE
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SPLIT
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e1fe4e9054c0..a8d40db096dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -68,13 +68,13 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
import org.junit.After;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index bfb760b6fc8c..248393cef9ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -12,7 +12,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.util.SplitBounds;
+import com.android.wm.shell.shared.split.SplitBounds;
import org.junit.Before;
import org.junit.Test;
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 f7ac3e416938..da0aca7b3b0f 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
@@ -79,12 +79,12 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index ac75c077b58f..c1c30f5379ab 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -49,6 +49,15 @@ inline bool letter_spacing_justification() {
#endif // __ANDROID__
}
+inline bool typeface_redesign() {
+#ifdef __ANDROID__
+ static bool flag = com_android_text_flags_typeface_redesign();
+ return flag;
+#else
+ return true;
+#endif // __ANDROID__
+}
+
} // namespace text_feature
} // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index f8574ee50525..1510ce1378d8 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -27,6 +27,8 @@
#include <cutils/compiler.h>
#include <log/log.h>
#include <minikin/Layout.h>
+
+#include "FeatureFlags.h"
#include "MinikinSkia.h"
#include "Paint.h"
#include "Typeface.h"
@@ -71,27 +73,42 @@ public:
static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
float saveSkewX = paint->getSkFont().getSkewX();
bool savefakeBold = paint->getSkFont().isEmbolden();
- const minikin::MinikinFont* curFont = nullptr;
- size_t start = 0;
- size_t nGlyphs = layout.nGlyphs();
- for (size_t i = 0; i < nGlyphs; i++) {
- const minikin::MinikinFont* nextFont = layout.typeface(i).get();
- if (i > 0 && nextFont != curFont) {
+ if (text_feature::typeface_redesign()) {
+ for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) {
+ uint32_t start = layout.getFontRunStart(runIdx);
+ uint32_t end = layout.getFontRunEnd(runIdx);
+ const minikin::FakedFont& fakedFont = layout.getFontRunFont(runIdx);
+
+ std::shared_ptr<minikin::MinikinFont> font = fakedFont.typeface();
+ SkFont* skfont = &paint->getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, font.get(), fakedFont.fakery);
+ f(start, end);
+ skfont->setSkewX(saveSkewX);
+ skfont->setEmbolden(savefakeBold);
+ }
+ } else {
+ const minikin::MinikinFont* curFont = nullptr;
+ size_t start = 0;
+ size_t nGlyphs = layout.nGlyphs();
+ for (size_t i = 0; i < nGlyphs; i++) {
+ const minikin::MinikinFont* nextFont = layout.typeface(i).get();
+ if (i > 0 && nextFont != curFont) {
+ SkFont* skfont = &paint->getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+ f(start, i);
+ skfont->setSkewX(saveSkewX);
+ skfont->setEmbolden(savefakeBold);
+ start = i;
+ }
+ curFont = nextFont;
+ }
+ if (nGlyphs > start) {
SkFont* skfont = &paint->getSkFont();
MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
- f(start, i);
+ f(start, nGlyphs);
skfont->setSkewX(saveSkewX);
skfont->setEmbolden(savefakeBold);
- start = i;
}
- curFont = nextFont;
- }
- if (nGlyphs > start) {
- SkFont* skfont = &paint->getSkFont();
- MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
- f(start, nGlyphs);
- skfont->setSkewX(saveSkewX);
- skfont->setEmbolden(savefakeBold);
}
}
};
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index fd596d998dfd..e427c97e41fa 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -16,6 +16,7 @@
#include "tests/common/TestContext.h"
+#include <com_android_graphics_libgui_flags.h>
#include <cutils/trace.h>
namespace android {
@@ -101,6 +102,14 @@ void TestContext::createWindowSurface() {
}
void TestContext::createOffscreenSurface() {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ mConsumer = new BufferItemConsumer(GRALLOC_USAGE_HW_COMPOSER, 4);
+ const ui::Size& resolution = getActiveDisplayResolution();
+ mConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
+ mSurface = mConsumer->getSurface();
+ mSurface->setMaxDequeuedBufferCount(3);
+ mSurface->setAsyncMode(true);
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
@@ -110,6 +119,7 @@ void TestContext::createOffscreenSurface() {
const ui::Size& resolution = getActiveDisplayResolution();
mConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
mSurface = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
}
void TestContext::waitForVsync() {
@@ -144,4 +154,4 @@ void TestContext::waitForVsync() {
} // namespace test
} // namespace uirenderer
-} // namespace android
+} // namespace android \ No newline at end of file
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ca468fc1ff44..029e6f49b062 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -10128,6 +10128,24 @@ public class AudioManager {
/**
* @hide
+ * Blocks until permission updates have propagated through the audio system.
+ * Only useful in tests, where adoptShellPermissions can change the permission state of
+ * an app without the app being killed.
+ */
+ @TestApi
+ @SuppressWarnings("UnflaggedApi") // @TestApi without associated feature.
+ public void permissionUpdateBarrier() {
+ final IAudioService service = getService();
+ try {
+ service.permissionUpdateBarrier();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * @hide
* Return the list of independent stream types for volume control.
* A stream type is considered independent when the volume changes of that type do not
* affect any other independent volume control stream type.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d20b7f090e92..e0c346100d03 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -101,6 +101,8 @@ interface IAudioService {
oneway void portEvent(in int portId, in int event, in @nullable PersistableBundle extras);
+ void permissionUpdateBarrier();
+
// Java-only methods below.
void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage);
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 2c71ee01b3f1..d14275ff2fd6 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -18,6 +18,7 @@ package android.media.tv.tuner;
import android.annotation.BytesLong;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -32,6 +33,7 @@ import android.hardware.tv.tuner.Constant64Bit;
import android.hardware.tv.tuner.FrontendScanType;
import android.media.MediaCodec;
import android.media.tv.TvInputService;
+import android.media.tv.flags.Flags;
import android.media.tv.tuner.dvr.DvrPlayback;
import android.media.tv.tuner.dvr.DvrRecorder;
import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener;
@@ -2529,6 +2531,50 @@ public class Tuner implements AutoCloseable {
}
/**
+ * Request a frontend by frontend type.
+ *
+ * <p> This API is used if the applications want to select a frontend with desired type when
+ * there are multiple frontends of the same type is there before {@link tune}. The applied
+ * frontend will be one of the not in-use frontend. If all frontends are in-use, this API will
+ * reclaim and apply the frontend owned by the lowest priority client if current client has
+ * higher priority. Otherwise, this API will not apply any frontend and return
+ * {@link #RESULT_UNAVAILABLE}.
+ *
+ * @param desiredFrontendType the Type of the desired fronted. Should be one of
+ * {@link android.media.tv.tuner.frontend.FrontendSettings.Type}
+ * @return result status of open operation.
+ */
+ @Result
+ @FlaggedApi(Flags.FLAG_TUNER_W_APIS)
+ @RequiresPermission(
+ allOf = {"android.permission.TUNER_RESOURCE_ACCESS", "android.permission.ACCESS_TV_TUNER"})
+ public int applyFrontendByType(@FrontendSettings.Type int desiredFrontendType) {
+ mFrontendLock.lock();
+ try {
+ if (mFeOwnerTuner != null) {
+ Log.e(TAG, "Operation connot be done by sharee of tuner");
+ return RESULT_INVALID_STATE;
+ }
+ if (mFrontendHandle != null) {
+ Log.e(TAG, "A frontend has been opened before");
+ return RESULT_INVALID_STATE;
+ }
+
+ mDesiredFrontendId = null;
+ mFrontendType = desiredFrontendType;
+ if (DEBUG) {
+ Log.d(TAG, "Applying frontend with type " + mFrontendType);
+ }
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+ return RESULT_UNAVAILABLE;
+ }
+ return RESULT_SUCCESS;
+ } finally {
+ mFrontendLock.unlock();
+ }
+ }
+
+ /**
* Open a shared filter instance.
*
* @param context the context of the caller.
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 371e3d2deda5..019b1e0de4d6 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -17,35 +17,31 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "ImageReader_JNI"
#define ATRACE_TAG ATRACE_TAG_CAMERA
-#include "android_media_Utils.h"
-#include <cutils/atomic.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
-#include <utils/List.h>
-#include <utils/Trace.h>
-#include <utils/String8.h>
-
-#include <cstdio>
-
-#include <gui/BufferItemConsumer.h>
-#include <gui/Surface.h>
-
+#include <android/hardware_buffer_jni.h>
#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/android_view_Surface.h>
#include <android_runtime/android_graphics_GraphicBuffer.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <android_runtime/android_view_Surface.h>
+#include <com_android_graphics_libgui_flags.h>
+#include <cutils/atomic.h>
#include <grallocusage/GrallocUsageConversion.h>
-
-#include <private/android/AHardwareBufferHelpers.h>
-
+#include <gui/BufferItemConsumer.h>
+#include <gui/Surface.h>
+#include <inttypes.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-
+#include <private/android/AHardwareBufferHelpers.h>
#include <stdint.h>
-#include <inttypes.h>
-#include <android/hardware_buffer_jni.h>
-
#include <ui/Rect.h>
+#include <utils/List.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/Trace.h>
+#include <utils/misc.h>
+
+#include <cstdio>
+
+#include "android_media_Utils.h"
#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext"
#define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID "mNativeBuffer"
@@ -393,18 +389,25 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint w
}
sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages));
- sp<IGraphicBufferProducer> gbProducer;
- sp<IGraphicBufferConsumer> gbConsumer;
- BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
- sp<BufferItemConsumer> bufferConsumer;
String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
width, height, nativeHalFormat, maxImages, getpid(),
createProcessUniqueId());
uint64_t consumerUsage =
android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ sp<BufferItemConsumer> bufferConsumer = new BufferItemConsumer(consumerUsage, maxImages,
+ /*controlledByApp*/ true);
+ sp<IGraphicBufferProducer> gbProducer =
+ bufferConsumer->getSurface()->getIGraphicBufferProducer();
+#else
+ sp<IGraphicBufferProducer> gbProducer;
+ sp<IGraphicBufferConsumer> gbConsumer;
+ BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
+ sp<BufferItemConsumer> bufferConsumer;
bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages,
/*controlledByApp*/true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
if (bufferConsumer == nullptr) {
jniThrowExceptionFmt(env, "java/lang/RuntimeException",
"Failed to allocate native buffer consumer for hal format 0x%x and usage 0x%x",
@@ -413,7 +416,11 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint w
}
if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ bufferConsumer->setConsumerIsProtected(true);
+#else
gbConsumer->setConsumerIsProtected(true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
}
ctx->setBufferConsumer(bufferConsumer);
diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
index 1bb82f88ba48..4637ccdfad15 100644
--- a/media/mca/filterfw/native/core/gl_env.cpp
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -15,21 +15,23 @@
*/
// #define LOG_NDEBUG 0
-#include "base/logging.h"
-#include "base/utilities.h"
#include "core/gl_env.h"
-#include "core/shader_program.h"
-#include "core/vertex_frame.h"
-#include "system/window.h"
-#include <map>
-#include <string>
#include <EGL/eglext.h>
-
+#include <com_android_graphics_libgui_flags.h>
#include <gui/BufferQueue.h>
-#include <gui/Surface.h>
#include <gui/GLConsumer.h>
#include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
+
+#include <map>
+#include <string>
+
+#include "base/logging.h"
+#include "base/utilities.h"
+#include "core/shader_program.h"
+#include "core/vertex_frame.h"
+#include "system/window.h"
namespace android {
namespace filterfw {
@@ -165,12 +167,18 @@ bool GLEnv::InitWithNewContext() {
}
// Create dummy surface using a GLConsumer
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ surfaceTexture_ = new GLConsumer(0, GLConsumer::TEXTURE_EXTERNAL, /*useFenceSync=*/true,
+ /*isControlledByApp=*/false);
+ window_ = surfaceTexture_->getSurface();
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
surfaceTexture_ = new GLConsumer(consumer, 0, GLConsumer::TEXTURE_EXTERNAL,
true, false);
window_ = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
if (CheckEGLError("eglCreateWindowSurface")) return false;
diff --git a/media/tests/mediatestutils/Android.bp b/media/tests/mediatestutils/Android.bp
index 88938e2f71d0..8c68f210ce3d 100644
--- a/media/tests/mediatestutils/Android.bp
+++ b/media/tests/mediatestutils/Android.bp
@@ -27,9 +27,11 @@ java_library {
name: "mediatestutils",
srcs: [
"java/com/android/media/mediatestutils/TestUtils.java",
+ "java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java",
],
static_libs: [
"androidx.concurrent_concurrent-futures",
+ "androidx.test.runner",
"guava",
"mediatestutils_host",
],
diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java
new file mode 100644
index 000000000000..c51b5dea9546
--- /dev/null
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.mediatestutils;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Barrier to wait for permission updates to propagate to audioserver, to avoid flakiness when using
+ * {@code com.android.compatability.common.util.AdoptShellPermissionsRule}. Note, this rule should
+ * <b> always </b> be placed after the adopt permission rule. Don't use rule when changing
+ * permission state in {@code @Before}, since that executes after all rules.
+ */
+public class PermissionUpdateBarrierRule implements TestRule {
+
+ private final Context mContext;
+
+ /**
+ * @param context the context to use
+ */
+ public PermissionUpdateBarrierRule(Context context) {
+ mContext = context;
+ }
+
+ public PermissionUpdateBarrierRule() {
+ this(InstrumentationRegistry.getInstrumentation().getContext());
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ mContext.getSystemService(AudioManager.class).permissionUpdateBarrier();
+ base.evaluate();
+ }
+ };
+ }
+}
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index a0b34690696f..08155dd3e09a 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -133,7 +133,8 @@
<LinearLayout
android:id="@+id/negative_multiple_devices_layout"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="match_parent"
+ android:padding="6dp"
android:gravity="center"
android:visibility="gone">
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index e8e24f492005..fe7cfc64603a 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -103,11 +103,10 @@
<style name="NegativeButtonMultipleDevices"
parent="@android:style/Widget.Material.Button.Colored">
<item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">36dp</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:minHeight">36dp</item>>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
- <item name="android:paddingLeft">6dp</item>
- <item name="android:paddingRight">6dp</item>
<item name="android:background">@drawable/btn_negative_multiple_devices</item>
<item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
</style>
diff --git a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
index ac47fbda09c6..391b16d38f0f 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
+++ b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
@@ -23,7 +23,6 @@ import static android.view.MotionEvent.ACTION_UP;
import android.app.Activity;
import android.content.res.Configuration;
-import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
@@ -38,9 +37,7 @@ import android.widget.Magnifier;
import com.android.egg.R;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.stream.IntStream;
public class PaintActivity extends Activity {
@@ -60,31 +57,28 @@ public class PaintActivity extends Activity {
private View.OnClickListener buttonHandler = new View.OnClickListener() {
@Override
public void onClick(View view) {
- switch (view.getId()) {
- case R.id.btnBrush:
- view.setSelected(true);
- hideToolbar(colors);
- toggleToolbar(brushes);
- break;
- case R.id.btnColor:
- view.setSelected(true);
- hideToolbar(brushes);
- toggleToolbar(colors);
- break;
- case R.id.btnClear:
- painting.clear();
- break;
- case R.id.btnSample:
- sampling = true;
- view.setSelected(true);
- break;
- case R.id.btnZen:
- painting.setZenMode(!painting.getZenMode());
- view.animate()
- .setStartDelay(200)
- .setInterpolator(new OvershootInterpolator())
- .rotation(painting.getZenMode() ? 0f : 90f);
- break;
+ // With non final fields in the R class we can't switch on the
+ // id since the case values are no longer constants.
+ int viewId = view.getId();
+ if (viewId == R.id.btnBrush) {
+ view.setSelected(true);
+ hideToolbar(colors);
+ toggleToolbar(brushes);
+ } else if (viewId == R.id.btnColor) {
+ view.setSelected(true);
+ hideToolbar(brushes);
+ toggleToolbar(colors);
+ } else if (viewId == R.id.btnClear) {
+ painting.clear();
+ } else if (viewId == R.id.btnSample) {
+ sampling = true;
+ view.setSelected(true);
+ } else if (viewId == R.id.btnZen) {
+ painting.setZenMode(!painting.getZenMode());
+ view.animate()
+ .setStartDelay(200)
+ .setInterpolator(new OvershootInterpolator())
+ .rotation(painting.getZenMode() ? 0f : 90f);
}
}
};
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index d36b55f8961a..9fa8fc3cc647 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -286,8 +286,7 @@ public class ZenMode implements Parcelable {
/**
* Returns the mode icon -- which can be either app-provided (via {@code addAutomaticZenRule}),
- * user-chosen (via the icon picker in Settings), the app's launcher icon for implicit rules
- * (in its monochrome variant, if available), or a default icon based on the mode type.
+ * user-chosen (via the icon picker in Settings), or a default icon based on the mode type.
*/
@NonNull
public ListenableFuture<Drawable> getIcon(@NonNull Context context,
@@ -300,23 +299,6 @@ public class ZenMode implements Parcelable {
return iconLoader.getIcon(context, mRule);
}
- /**
- * Returns an alternative mode icon. The difference with {@link #getIcon} is that it's the
- * basic DND icon not only for Manual DND, but also for <em>implicit rules</em>. As such, it's
- * suitable for places where showing the launcher icon of an app could be confusing, such as
- * the status bar or lockscreen.
- */
- @NonNull
- public ListenableFuture<Drawable> getLockscreenIcon(@NonNull Context context,
- @NonNull ZenIconLoader iconLoader) {
- if (mKind == Kind.MANUAL_DND || mKind == Kind.IMPLICIT) {
- return Futures.immediateFuture(requireNonNull(
- context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
- }
-
- return iconLoader.getIcon(context, mRule);
- }
-
@NonNull
public ZenPolicy getPolicy() {
switch (mRule.getInterruptionFilter()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 6198d80cefe6..d71b337228f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -181,7 +181,7 @@ public class CreateUserDialogController {
* admin status.
*/
public Dialog createDialog(Activity activity,
- ActivityStarter activityStarter, boolean canCreateAdminUser,
+ @NonNull ActivityStarter activityStarter, boolean canCreateAdminUser,
NewUserData successCallback, Runnable cancelCallback) {
mActivity = activity;
mCustomDialogHelper = new CustomDialogHelper(activity);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 46f229035839..c4c4ed8e7b91 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -31,6 +31,7 @@ import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -126,9 +127,11 @@ public class EditUserInfoController {
* @param activityStarter - ActivityStarter is called with appropriate intents and request
* codes to take photo/choose photo/crop photo.
*/
- public Dialog createDialog(Activity activity, ActivityStarter activityStarter,
- @Nullable Drawable oldUserIcon, String defaultUserName,
- BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
+ public @NonNull Dialog createDialog(@NonNull Activity activity,
+ @NonNull ActivityStarter activityStarter, @Nullable Drawable oldUserIcon,
+ @Nullable String defaultUserName,
+ @Nullable BiConsumer<String, Drawable> successCallback,
+ @Nullable Runnable cancelCallback) {
LayoutInflater inflater = LayoutInflater.from(activity);
View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS
new file mode 100644
index 000000000000..134a56ecb27e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS
@@ -0,0 +1 @@
+include /packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index f533e77bb33e..32216fadfb0d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -322,41 +322,6 @@ public class ZenModeTest {
verify(iconLoader).getIcon(any(), eq(IMPLICIT_ZEN_RULE));
}
- @Test
- public void getLockscreenIcon_normalMode_loadsIconNormally() {
- ZenIconLoader iconLoader = mock(ZenIconLoader.class);
- ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));
-
- ListenableFuture<Drawable> unused = mode.getLockscreenIcon(
- RuntimeEnvironment.getApplication(), iconLoader);
-
- verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
- }
-
- @Test
- public void getLockscreenIcon_manualDnd_returnsFixedIcon() {
- ZenIconLoader iconLoader = mock(ZenIconLoader.class);
-
- ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getLockscreenIcon(
- RuntimeEnvironment.getApplication(), iconLoader);
-
- assertThat(future.isDone()).isTrue();
- verify(iconLoader, never()).getIcon(any(), any());
- }
-
- @Test
- public void getLockscreenIcon_implicitMode_returnsFixedIcon() {
- ZenIconLoader iconLoader = mock(ZenIconLoader.class);
- ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
- zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));
-
- ListenableFuture<Drawable> future = mode.getLockscreenIcon(
- RuntimeEnvironment.getApplication(), iconLoader);
-
- assertThat(future.isDone()).isTrue();
- verify(iconLoader, never()).getIcon(any(), any());
- }
-
private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
Parcel parcel = Parcel.obtain();
try {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 006e644b2ac7..62401a124ad1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -81,3 +81,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "sync_local_overrides_removal_new_storage"
+ namespace: "core_experiments_team_internal"
+ description: "When DeviceConfig overrides are deleted, delete new storage overrides too."
+ bug: "361643653"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0b364ac4a176..8a1d81be5e11 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1032,6 +1032,16 @@ flag {
}
flag {
+ name: "communal_edit_widgets_activity_finish_fix"
+ namespace: "systemui"
+ description: "finish edit widgets activity when stopping"
+ bug: "354725145"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "app_clips_backlinks"
namespace: "systemui"
description: "Enables Backlinks improvement feature in App Clips"
@@ -1083,6 +1093,13 @@ flag {
}
flag {
+ name: "media_controls_posts_optimization"
+ namespace: "systemui"
+ description: "Ignore duplicate media notifications posted"
+ bug: "358645640"
+}
+
+flag {
namespace: "systemui"
name: "enable_view_capture_tracing"
description: "Enables view capture tracing in System UI."
@@ -1103,16 +1120,6 @@ flag {
}
flag {
- name: "glanceable_hub_back_gesture"
- namespace: "systemui"
- description: "Enables back gesture on the glanceable hub"
- bug: "346331399"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "glanceable_hub_allow_keyguard_when_dreaming"
namespace: "systemui"
description: "Allows users to exit dream to keyguard with glanceable hub enabled"
@@ -1367,3 +1374,13 @@ flag {
}
}
+flag {
+ name: "media_load_metadata_via_media_data_loader"
+ namespace: "systemui"
+ description: "Use MediaDataLoader for loading media metadata with better threading"
+ bug: "358350077"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/packages/SystemUI/animation/build.gradle b/packages/SystemUI/animation/build.gradle
index 939455fa44ac..16ba42f862f7 100644
--- a/packages/SystemUI/animation/build.gradle
+++ b/packages/SystemUI/animation/build.gradle
@@ -10,13 +10,6 @@ android {
}
}
- compileSdk 33
-
- defaultConfig {
- minSdk 33
- targetSdk 33
- }
-
lintOptions {
abortOnError false
}
@@ -24,10 +17,6 @@ android {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
- kotlinOptions {
- jvmTarget = '1.8'
- freeCompilerArgs = ["-Xjvm-default=all"]
- }
}
dependencies {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index aeba67bd121c..5a4020d3b1fb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -31,6 +31,7 @@ import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneActionsViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
@@ -56,7 +57,7 @@ constructor(
private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory,
private val contentViewModelFactory: BouncerSceneContentViewModel.Factory,
private val dialogFactory: BouncerDialogFactory,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Bouncer
private val actionsViewModel: BouncerSceneActionsViewModel by lazy {
@@ -66,7 +67,7 @@ constructor(
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 872bef256f3a..ed1277666372 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -31,7 +31,6 @@ import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
@@ -47,7 +46,6 @@ import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.internal.R.attr.focusable
-import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -198,15 +196,7 @@ fun CommunalContainer(
Box(modifier = Modifier.fillMaxSize())
}
- val userActions =
- if (glanceableHubBackGesture()) {
- mapOf(
- Swipe(SwipeDirection.End) to CommunalScenes.Blank,
- Back to CommunalScenes.Blank,
- )
- } else {
- mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
- }
+ val userActions = mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
scene(CommunalScenes.Communal, userActions = userActions) {
CommunalScene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 91a88bc7aa97..b0590e06d3bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -545,18 +545,35 @@ private fun ScrollOnUpdatedLiveContentEffect(
) {
val coroutineScope = rememberCoroutineScope()
val liveContentKeys = remember { mutableListOf<String>() }
+ var communalContentPending by remember { mutableStateOf(true) }
LaunchedEffect(communalContent) {
+ // Do nothing until any communal content comes in
+ if (communalContentPending && communalContent.isEmpty()) {
+ return@LaunchedEffect
+ }
+
val prevLiveContentKeys = liveContentKeys.toList()
+ val newLiveContentKeys = communalContent.filter { it.isLiveContent() }.map { it.key }
liveContentKeys.clear()
- liveContentKeys.addAll(communalContent.filter { it.isLiveContent() }.map { it.key })
+ liveContentKeys.addAll(newLiveContentKeys)
- // Find the first updated content
+ // Do nothing on first communal content since we don't have a delta
+ if (communalContentPending) {
+ communalContentPending = false
+ return@LaunchedEffect
+ }
+
+ // Do nothing if there is no new live content
val indexOfFirstUpdatedContent =
- liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
+ newLiveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
+ if (indexOfFirstUpdatedContent < 0) {
+ return@LaunchedEffect
+ }
- // Scroll if current position is behind the first updated content
- if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
+ // Scroll if the live content is not visible
+ val lastVisibleItemIndex = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
+ if (lastVisibleItemIndex != null && indexOfFirstUpdatedContent > lastVisibleItemIndex) {
// Launching with a scope to prevent the job from being canceled in the case of a
// recomposition during scrolling
coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
@@ -1154,7 +1171,7 @@ private fun WidgetContent(
.then(selectableModifier)
.thenIf(!viewModel.isEditMode && !model.inQuietMode) {
Modifier.pointerInput(Unit) {
- observeTaps { viewModel.onTapWidget(model.componentName, model.priority) }
+ observeTaps { viewModel.onTapWidget(model.componentName, model.rank) }
}
}
.thenIf(!viewModel.isEditMode && model.inQuietMode) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 6750e41009c7..54ffcf475680 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -27,10 +27,12 @@ import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSe
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -44,7 +46,7 @@ constructor(
private val dialogFactory: SystemUIDialogFactory,
private val interactionHandler: WidgetInteractionHandler,
private val widgetSection: CommunalAppWidgetSection,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Communal
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
@@ -55,6 +57,10 @@ constructor(
)
.asStateFlow()
+ override suspend fun onActivated(): Nothing {
+ awaitCancellation()
+ }
+
@Composable
override fun SceneScope.Content(modifier: Modifier) {
CommunalHub(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 38a347465318..11373577eedb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -34,11 +34,11 @@ fun rememberContentListState(
return remember(communalContent) {
ContentListState(
communalContent,
- { componentName, user, priority ->
+ { componentName, user, rank ->
viewModel.onAddWidget(
componentName,
user,
- priority,
+ rank,
widgetConfigurator,
)
},
@@ -56,10 +56,9 @@ fun rememberContentListState(
class ContentListState
internal constructor(
communalContent: List<CommunalContentModel>,
- private val onAddWidget:
- (componentName: ComponentName, user: UserHandle, priority: Int) -> Unit,
- private val onDeleteWidget: (id: Int, componentName: ComponentName, priority: Int) -> Unit,
- private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
+ private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
+ private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
+ private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
) {
var list = communalContent.toMutableStateList()
private set
@@ -74,7 +73,7 @@ internal constructor(
if (list[indexToRemove].isWidgetContent()) {
val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
list.apply { removeAt(indexToRemove) }
- onDeleteWidget(widget.appWidgetId, widget.componentName, widget.priority)
+ onDeleteWidget(widget.appWidgetId, widget.componentName, widget.rank)
}
}
@@ -94,24 +93,24 @@ internal constructor(
newItemUser: UserHandle? = null,
newItemIndex: Int? = null
) {
- // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
- // in the list. When persisted in DB, this leaves space for the new item (to be added) at
- // the correct priority.
- val widgetIdToPriorityMap: Map<Int, Int> =
+ // New widget added to the grid. Other widgets are shifted as needed at the database level.
+ if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
+ onAddWidget(newItemComponentName, newItemUser, /* rank= */ newItemIndex)
+ return
+ }
+
+ // No new widget, only reorder existing widgets.
+ val widgetIdToRankMap: Map<Int, Int> =
list
.mapIndexedNotNull { index, item ->
if (item is CommunalContentModel.WidgetContent) {
- item.appWidgetId to list.size - index
+ item.appWidgetId to index
} else {
null
}
}
.toMap()
- // reorder and then add the new widget
- onReorderWidgets(widgetIdToPriorityMap)
- if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
- onAddWidget(newItemComponentName, newItemUser, /* priority= */ list.size - newItemIndex)
- }
+ onReorderWidgets(widgetIdToRankMap)
}
/** Returns true if the item at given index is editable. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 0c293948dd3c..f2f7c872b71c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -193,7 +193,7 @@ internal class DragAndDropTargetState(
val widgetExtra = event.maybeWidgetExtra() ?: return false
val (componentName, user) = widgetExtra
if (componentName != null && user != null) {
- // Placeholder isn't removed yet to allow the setting the right priority for items
+ // Placeholder isn't removed yet to allow the setting the right rank for items
// before adding in the new item.
contentListState.onSaveList(
newItemComponentName = componentName,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 7f059d766307..2029e9e7f139 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -25,6 +25,7 @@ import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
@@ -39,7 +40,7 @@ class LockscreenScene
constructor(
actionsViewModelFactory: LockscreenSceneActionsViewModel.Factory,
private val lockscreenContent: Lazy<LockscreenContent>,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Lockscreen
private val actionsViewModel: LockscreenSceneActionsViewModel by lazy {
@@ -49,7 +50,7 @@ constructor(
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index a3e07016ced1..3e73057ba09b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -76,6 +76,11 @@ constructor(
viewModel
.areNotificationsVisible(contentKey)
.collectAsStateWithLifecycle(initialValue = false)
+ val isBypassEnabled by viewModel.isBypassEnabled.collectAsStateWithLifecycle()
+
+ if (isBypassEnabled) {
+ with(notificationSection) { HeadsUpNotifications() }
+ }
LockscreenLongPress(
viewModel = viewModel.touchHandling,
@@ -110,7 +115,7 @@ constructor(
}
)
}
- if (isShadeLayoutWide) {
+ if (isShadeLayoutWide && !isBypassEnabled) {
with(notificationSection) {
Notifications(
areNotificationsVisible = areNotificationsVisible,
@@ -124,7 +129,7 @@ constructor(
}
}
}
- if (!isShadeLayoutWide) {
+ if (!isShadeLayoutWide && !isBypassEnabled) {
with(notificationSection) {
Notifications(
areNotificationsVisible = areNotificationsVisible,
@@ -175,7 +180,7 @@ constructor(
},
modifier = Modifier.fillMaxSize(),
) { measurables, constraints ->
- check(measurables.size == 6)
+ check(measurables.size == 6) { "Expected 6 measurables, got: ${measurables.size}" }
val aboveLockIconMeasurable = measurables[0]
val lockIconMeasurable = measurables[1]
val belowLockIconMeasurable = measurables[2]
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6801cf27a64d..5f4dc6e77c63 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -20,7 +20,6 @@ import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp
@@ -34,6 +33,7 @@ import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -79,6 +79,14 @@ constructor(
)
}
+ @Composable
+ fun SceneScope.HeadsUpNotifications() {
+ SnoozeableHeadsUpNotificationSpace(
+ stackScrollView = stackScrollView.get(),
+ viewModel = rememberViewModel { viewModelFactory.create() },
+ )
+ }
+
/**
* @param burnInParams params to make this view adaptive to burn-in, `null` to disable burn-in
* adjustment
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index 666e324c8d36..62213bd22cbd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -28,6 +28,7 @@ import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
import com.android.systemui.scene.session.ui.composable.SaveableSession
@@ -61,7 +62,7 @@ constructor(
private val shadeSession: SaveableSession,
private val stackScrollView: Lazy<NotificationScrollView>,
private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.NotificationsShade
@@ -72,7 +73,7 @@ constructor(
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index fc4a8a5ee67c..192162475c9f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -121,6 +121,8 @@ private fun SceneScope.stateForQuickSettingsContent(
)
}
}
+ is TransitionState.Transition.OverlayTransition ->
+ TODO("b/359173565: Handle overlay transitions")
}
}
@@ -212,7 +214,8 @@ private fun QuickSettingsContent(
addView(view)
}
},
- // When the view changes (e.g. due to a theme change), this will be recomposed
+ // When the view changes (e.g. due to a theme change), this will be
+ // recomposed
// if needed and the new view will be attached to the FrameLayout here.
update = {
qsSceneAdapter.setState(state())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d16ba0d6affb..f11f8bb94a52 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -81,6 +81,7 @@ import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -129,7 +130,7 @@ constructor(
private val statusBarIconController: StatusBarIconController,
private val mediaCarouselController: MediaCarouselController,
@Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.QuickSettings
private val actionsViewModel: QuickSettingsSceneActionsViewModel by lazy {
@@ -139,7 +140,7 @@ constructor(
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index fb7c42254caa..b25773b68471 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -43,6 +43,7 @@ import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -74,7 +75,7 @@ constructor(
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.QuickSettingsShade
@@ -85,6 +86,10 @@ constructor(
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
+ override suspend fun onActivated(): Nothing {
+ actionsViewModel.activate()
+ }
+
@Composable
override fun SceneScope.Content(
modifier: Modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 3e221056c2db..d489d731b5fb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -26,6 +26,7 @@ import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.ui.composable.QuickSettings
@@ -50,7 +51,7 @@ constructor(
private val notificationStackScrolLView: Lazy<NotificationScrollView>,
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
private val viewModelFactory: GoneSceneActionsViewModel.Factory,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Gone
private val actionsViewModel: GoneSceneActionsViewModel by lazy { viewModelFactory.create() }
@@ -58,7 +59,7 @@ constructor(
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
actionsViewModel.actions
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 853dc6fb66cc..f8513a8c4dd4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -82,6 +82,7 @@ import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.composable.MediaContentPicker
@@ -160,7 +161,7 @@ constructor(
private val mediaCarouselController: MediaCarouselController,
@Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
@Named(QS_PANEL) private val qsMediaHost: MediaHost,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
override val key = Scenes.Shade
@@ -168,7 +169,7 @@ constructor(
actionsViewModelFactory.create()
}
- override suspend fun activate(): Nothing {
+ override suspend fun onActivated(): Nothing {
actionsViewModel.activate()
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index 2bc8c87978a8..b16673702b49 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -26,15 +26,15 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
internal fun CoroutineScope.animateContent(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
transition: TransitionState.Transition,
oneOffAnimation: OneOffAnimation,
targetProgress: Float,
- startTransition: () -> Unit,
- finishTransition: () -> Unit,
+ chain: Boolean = true,
) {
// Start the transition. This will compute the TransformationSpec associated to [transition],
// which we need to initialize the Animatable that will actually animate it.
- startTransition()
+ layoutState.startTransition(transition, chain)
// The transition now contains the transformation spec that we should use to instantiate the
// Animatable.
@@ -59,7 +59,7 @@ internal fun CoroutineScope.animateContent(
try {
animatable.animateTo(targetProgress, animationSpec, initialVelocity)
} finally {
- finishTransition()
+ layoutState.finishTransition(transition)
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
new file mode 100644
index 000000000000..e020f14a9a02
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import com.android.compose.animation.scene.content.state.TransitionState
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+
+/** Trigger a one-off transition to show or hide an overlay. */
+internal fun CoroutineScope.showOrHideOverlay(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
+ overlay: OverlayKey,
+ fromOrToScene: SceneKey,
+ isShowing: Boolean,
+ transitionKey: TransitionKey?,
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay?,
+ reversed: Boolean,
+): TransitionState.Transition.ShowOrHideOverlay {
+ val targetProgress = if (reversed) 0f else 1f
+ val (fromContent, toContent) =
+ if (isShowing xor reversed) {
+ fromOrToScene to overlay
+ } else {
+ overlay to fromOrToScene
+ }
+
+ val oneOffAnimation = OneOffAnimation()
+ val transition =
+ OneOffShowOrHideOverlayTransition(
+ overlay = overlay,
+ fromOrToScene = fromOrToScene,
+ fromContent = fromContent,
+ toContent = toContent,
+ isEffectivelyShown = isShowing,
+ key = transitionKey,
+ replacedTransition = replacedTransition,
+ oneOffAnimation = oneOffAnimation,
+ )
+
+ animateContent(
+ layoutState = layoutState,
+ transition = transition,
+ oneOffAnimation = oneOffAnimation,
+ targetProgress = targetProgress,
+ )
+
+ return transition
+}
+
+/** Trigger a one-off transition to replace an overlay by another one. */
+internal fun CoroutineScope.replaceOverlay(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
+ fromOverlay: OverlayKey,
+ toOverlay: OverlayKey,
+ transitionKey: TransitionKey?,
+ replacedTransition: TransitionState.Transition.ReplaceOverlay?,
+ reversed: Boolean,
+): TransitionState.Transition.ReplaceOverlay {
+ val targetProgress = if (reversed) 0f else 1f
+ val effectivelyShownOverlay = if (reversed) fromOverlay else toOverlay
+
+ val oneOffAnimation = OneOffAnimation()
+ val transition =
+ OneOffOverlayReplacingTransition(
+ fromOverlay = fromOverlay,
+ toOverlay = toOverlay,
+ effectivelyShownOverlay = effectivelyShownOverlay,
+ key = transitionKey,
+ replacedTransition = replacedTransition,
+ oneOffAnimation = oneOffAnimation,
+ )
+
+ animateContent(
+ layoutState = layoutState,
+ transition = transition,
+ oneOffAnimation = oneOffAnimation,
+ targetProgress = targetProgress,
+ )
+
+ return transition
+}
+
+private class OneOffShowOrHideOverlayTransition(
+ overlay: OverlayKey,
+ fromOrToScene: SceneKey,
+ fromContent: ContentKey,
+ toContent: ContentKey,
+ override val isEffectivelyShown: Boolean,
+ override val key: TransitionKey?,
+ replacedTransition: TransitionState.Transition?,
+ private val oneOffAnimation: OneOffAnimation,
+) :
+ TransitionState.Transition.ShowOrHideOverlay(
+ overlay,
+ fromOrToScene,
+ fromContent,
+ toContent,
+ replacedTransition,
+ ) {
+ override val progress: Float
+ get() = oneOffAnimation.progress
+
+ override val progressVelocity: Float
+ get() = oneOffAnimation.progressVelocity
+
+ override val isInitiatedByUserInput: Boolean = false
+ override val isUserInputOngoing: Boolean = false
+
+ override fun finish(): Job = oneOffAnimation.finish()
+}
+
+private class OneOffOverlayReplacingTransition(
+ fromOverlay: OverlayKey,
+ toOverlay: OverlayKey,
+ override val effectivelyShownOverlay: OverlayKey,
+ override val key: TransitionKey?,
+ replacedTransition: TransitionState.Transition?,
+ private val oneOffAnimation: OneOffAnimation,
+) : TransitionState.Transition.ReplaceOverlay(fromOverlay, toOverlay, replacedTransition) {
+ override val progress: Float
+ get() = oneOffAnimation.progress
+
+ override val progressVelocity: Float
+ get() = oneOffAnimation.progressVelocity
+
+ override val isInitiatedByUserInput: Boolean = false
+ override val isUserInputOngoing: Boolean = false
+
+ override fun finish(): Job = oneOffAnimation.finish()
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index ea708a5637f0..4aa50b586c1b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -393,7 +393,8 @@ private class AnimatedStateImpl<T, Delta>(
transition: TransitionState.Transition?,
): T? {
if (transition == null) {
- return sharedValue[layoutImpl.state.transitionState.currentScene]
+ return sharedValue[content]
+ ?: sharedValue[layoutImpl.state.transitionState.currentScene]
}
val fromValue = sharedValue[transition.fromContent]
@@ -411,7 +412,7 @@ private class AnimatedStateImpl<T, Delta>(
if (canOverflow) transition.progress
else transition.progress.fastCoerceIn(0f, 1f)
}
- overscrollSpec.scene == transition.toContent -> 1f
+ overscrollSpec.content == transition.toContent -> 1f
else -> 0f
}
@@ -424,10 +425,12 @@ private class AnimatedStateImpl<T, Delta>(
val targetValues = sharedValue.targetValues
val transition =
if (element != null) {
- layoutImpl.elements[element]?.stateByContent?.let { sceneStates ->
- layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
- transition.fromContent in sceneStates || transition.toContent in sceneStates
- }
+ layoutImpl.elements[element]?.let { element ->
+ elementState(
+ layoutImpl.state.transitionStates,
+ isInContent = { it in element.stateByContent },
+ )
+ as? TransitionState.Transition
}
} else {
layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index f2c2a3600366..abe079a4ab64 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -43,12 +43,15 @@ internal fun CoroutineScope.animateToScene(
}
return when (transitionState) {
- is TransitionState.Idle -> {
+ is TransitionState.Idle,
+ is TransitionState.Transition.ShowOrHideOverlay,
+ is TransitionState.Transition.ReplaceOverlay -> {
animateToScene(
layoutState,
target,
transitionKey,
isInitiatedByUserInput = false,
+ fromScene = transitionState.currentScene,
replacedTransition = null,
)
}
@@ -163,11 +166,11 @@ private fun CoroutineScope.animateToScene(
}
animateContent(
+ layoutState = layoutState,
transition = transition,
oneOffAnimation = oneOffAnimation,
targetProgress = targetProgress,
- startTransition = { layoutState.startTransition(transition, chain) },
- finishTransition = { layoutState.finishTransition(transition) },
+ chain = chain,
)
return transition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 7dac2e4c4ada..9b1740dc700a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -46,7 +46,7 @@ import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
import androidx.compose.ui.util.fastCoerceIn
-import androidx.compose.ui.util.fastLastOrNull
+import androidx.compose.ui.util.fastForEachReversed
import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.content.Content
import com.android.compose.animation.scene.content.state.TransitionState
@@ -145,8 +145,9 @@ internal fun Modifier.element(
// layout/drawing.
// TODO(b/341072461): Revert this and read the current transitions in ElementNode directly once
// we can ensure that SceneTransitionLayoutImpl will compose new contents first.
- val currentTransitions = layoutImpl.state.currentTransitions
- return then(ElementModifier(layoutImpl, currentTransitions, content, key)).testTag(key.testTag)
+ val currentTransitionStates = layoutImpl.state.transitionStates
+ return then(ElementModifier(layoutImpl, currentTransitionStates, content, key))
+ .testTag(key.testTag)
}
/**
@@ -155,20 +156,21 @@ internal fun Modifier.element(
*/
private data class ElementModifier(
private val layoutImpl: SceneTransitionLayoutImpl,
- private val currentTransitions: List<TransitionState.Transition>,
+ private val currentTransitionStates: List<TransitionState>,
private val content: Content,
private val key: ElementKey,
) : ModifierNodeElement<ElementNode>() {
- override fun create(): ElementNode = ElementNode(layoutImpl, currentTransitions, content, key)
+ override fun create(): ElementNode =
+ ElementNode(layoutImpl, currentTransitionStates, content, key)
override fun update(node: ElementNode) {
- node.update(layoutImpl, currentTransitions, content, key)
+ node.update(layoutImpl, currentTransitionStates, content, key)
}
}
internal class ElementNode(
private var layoutImpl: SceneTransitionLayoutImpl,
- private var currentTransitions: List<TransitionState.Transition>,
+ private var currentTransitionStates: List<TransitionState>,
private var content: Content,
private var key: ElementKey,
) : Modifier.Node(), DrawModifierNode, ApproachLayoutModifierNode, TraversableNode {
@@ -226,12 +228,12 @@ internal class ElementNode(
fun update(
layoutImpl: SceneTransitionLayoutImpl,
- currentTransitions: List<TransitionState.Transition>,
+ currentTransitionStates: List<TransitionState>,
content: Content,
key: ElementKey,
) {
check(layoutImpl == this.layoutImpl && content == this.content)
- this.currentTransitions = currentTransitions
+ this.currentTransitionStates = currentTransitionStates
removeNodeFromContentState()
@@ -287,31 +289,73 @@ internal class ElementNode(
measurable: Measurable,
constraints: Constraints,
): MeasureResult {
- val transitions = currentTransitions
- val transition = elementTransition(layoutImpl, element, transitions)
+ val elementState = elementState(layoutImpl, element, currentTransitionStates)
+ if (elementState == null) {
+ // If the element is not part of any transition, place it normally in its idle scene.
+ val currentState = currentTransitionStates.last()
+ val placeInThisContent =
+ elementContentWhenIdle(
+ layoutImpl,
+ currentState.currentScene,
+ currentState.currentOverlays,
+ isInContent = { it in element.stateByContent },
+ ) == content.key
+
+ return if (placeInThisContent) {
+ placeNormally(measurable, constraints)
+ } else {
+ doNotPlace(measurable, constraints)
+ }
+ }
+
+ val transition = elementState as? TransitionState.Transition
// If this element is not supposed to be laid out now, either because it is not part of any
// ongoing transition or the other content of its transition is overscrolling, then lay out
// the element normally and don't place it.
- val overscrollScene = transition?.currentOverscrollSpec?.scene
+ val overscrollScene = transition?.currentOverscrollSpec?.content
val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != content.key
- val isNotPartOfAnyOngoingTransitions = transitions.isNotEmpty() && transition == null
- if (isNotPartOfAnyOngoingTransitions || isOtherSceneOverscrolling) {
- recursivelyClearPlacementValues()
- stateInContent.lastSize = Element.SizeUnspecified
-
- val placeable = measurable.measure(constraints)
- return layout(placeable.width, placeable.height) { /* Do not place */ }
+ if (isOtherSceneOverscrolling) {
+ return doNotPlace(measurable, constraints)
}
val placeable =
measure(layoutImpl, element, transition, stateInContent, measurable, constraints)
stateInContent.lastSize = placeable.size()
- return layout(placeable.width, placeable.height) { place(transition, placeable) }
+ return layout(placeable.width, placeable.height) { place(elementState, placeable) }
+ }
+
+ private fun ApproachMeasureScope.doNotPlace(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ recursivelyClearPlacementValues()
+ stateInContent.lastSize = Element.SizeUnspecified
+
+ val placeable = measurable.measure(constraints)
+ return layout(placeable.width, placeable.height) { /* Do not place */ }
+ }
+
+ private fun ApproachMeasureScope.placeNormally(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val placeable = measurable.measure(constraints)
+ stateInContent.lastSize = placeable.size()
+ return layout(placeable.width, placeable.height) {
+ coordinates?.let {
+ with(layoutImpl.lookaheadScope) {
+ stateInContent.lastOffset =
+ lookaheadScopeCoordinates.localPositionOf(it, Offset.Zero)
+ }
+ }
+
+ placeable.place(0, 0)
+ }
}
private fun Placeable.PlacementScope.place(
- transition: TransitionState.Transition?,
+ elementState: TransitionState,
placeable: Placeable,
) {
with(layoutImpl.lookaheadScope) {
@@ -321,11 +365,12 @@ internal class ElementNode(
coordinates ?: error("Element ${element.key} does not have any coordinates")
// No need to place the element in this content if we don't want to draw it anyways.
- if (!shouldPlaceElement(layoutImpl, content.key, element, transition)) {
+ if (!shouldPlaceElement(layoutImpl, content.key, element, elementState)) {
recursivelyClearPlacementValues()
return
}
+ val transition = elementState as? TransitionState.Transition
val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
val targetOffset =
computeValue(
@@ -391,11 +436,15 @@ internal class ElementNode(
return@placeWithLayer
}
- val transition = elementTransition(layoutImpl, element, currentTransitions)
- if (!shouldPlaceElement(layoutImpl, content.key, element, transition)) {
+ val elementState = elementState(layoutImpl, element, currentTransitionStates)
+ if (
+ elementState == null ||
+ !shouldPlaceElement(layoutImpl, content.key, element, elementState)
+ ) {
return@placeWithLayer
}
+ val transition = elementState as? TransitionState.Transition
alpha = elementAlpha(layoutImpl, element, transition, stateInContent)
compositingStrategy = CompositingStrategy.ModulateAlpha
}
@@ -425,7 +474,9 @@ internal class ElementNode(
override fun ContentDrawScope.draw() {
element.wasDrawnInAnyContent = true
- val transition = elementTransition(layoutImpl, element, currentTransitions)
+ val transition =
+ elementState(layoutImpl, element, currentTransitionStates)
+ as? TransitionState.Transition
val drawScale = getDrawScale(layoutImpl, element, transition, stateInContent)
if (drawScale == Scale.Default) {
drawContent()
@@ -468,21 +519,15 @@ internal class ElementNode(
}
}
-/**
- * The transition that we should consider for [element]. This is the last transition where one of
- * its contents contains the element.
- */
-private fun elementTransition(
+/** The [TransitionState] that we should consider for [element]. */
+private fun elementState(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
- transitions: List<TransitionState.Transition>,
-): TransitionState.Transition? {
- val transition =
- transitions.fastLastOrNull { transition ->
- transition.fromContent in element.stateByContent ||
- transition.toContent in element.stateByContent
- }
+ transitionStates: List<TransitionState>,
+): TransitionState? {
+ val state = elementState(transitionStates, isInContent = { it in element.stateByContent })
+ val transition = state as? TransitionState.Transition
val previousTransition = element.lastTransition
element.lastTransition = transition
@@ -497,7 +542,66 @@ private fun elementTransition(
}
}
- return transition
+ return state
+}
+
+internal inline fun elementState(
+ transitionStates: List<TransitionState>,
+ isInContent: (ContentKey) -> Boolean,
+): TransitionState? {
+ val lastState = transitionStates.last()
+ if (lastState is TransitionState.Idle) {
+ check(transitionStates.size == 1)
+ return lastState
+ }
+
+ // Find the last transition with a content that contains the element.
+ transitionStates.fastForEachReversed { state ->
+ val transition = state as TransitionState.Transition
+ if (isInContent(transition.fromContent) || isInContent(transition.toContent)) {
+ return transition
+ }
+ }
+
+ return null
+}
+
+internal inline fun elementContentWhenIdle(
+ layoutImpl: SceneTransitionLayoutImpl,
+ idle: TransitionState.Idle,
+ isInContent: (ContentKey) -> Boolean,
+): ContentKey {
+ val currentScene = idle.currentScene
+ val overlays = idle.currentOverlays
+ return elementContentWhenIdle(layoutImpl, currentScene, overlays, isInContent)
+}
+
+private inline fun elementContentWhenIdle(
+ layoutImpl: SceneTransitionLayoutImpl,
+ currentScene: SceneKey,
+ overlays: Set<OverlayKey>,
+ isInContent: (ContentKey) -> Boolean,
+): ContentKey {
+ if (overlays.isEmpty()) {
+ return currentScene
+ }
+
+ // Find the overlay with highest zIndex that contains the element.
+ // TODO(b/353679003): Should we cache enabledOverlays into a List<> to avoid a lot of
+ // allocations here?
+ var currentOverlay: OverlayKey? = null
+ for (overlay in overlays) {
+ if (
+ isInContent(overlay) &&
+ (currentOverlay == null ||
+ (layoutImpl.overlay(overlay).zIndex >
+ layoutImpl.overlay(currentOverlay).zIndex))
+ ) {
+ currentOverlay = overlay
+ }
+ }
+
+ return currentOverlay ?: currentScene
}
private fun prepareInterruption(
@@ -693,12 +797,20 @@ private fun shouldPlaceElement(
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
- transition: TransitionState.Transition?,
+ elementState: TransitionState,
): Boolean {
- // Always place the element if we are idle.
- if (transition == null) {
- return true
- }
+ val transition =
+ when (elementState) {
+ is TransitionState.Idle -> {
+ return content ==
+ elementContentWhenIdle(
+ layoutImpl,
+ elementState,
+ isInContent = { it in element.stateByContent },
+ )
+ }
+ is TransitionState.Transition -> elementState
+ }
// Don't place the element in this content if this content is not part of the current element
// transition.
@@ -734,23 +846,19 @@ internal fun shouldPlaceOrComposeSharedElement(
transition: TransitionState.Transition,
): Boolean {
// If we are overscrolling, only place/compose the element in the overscrolling scene.
- val overscrollScene = transition.currentOverscrollSpec?.scene
+ val overscrollScene = transition.currentOverscrollSpec?.content
if (overscrollScene != null) {
return content == overscrollScene
}
val scenePicker = element.contentPicker
val pickedScene =
- when (transition) {
- is TransitionState.Transition.ChangeCurrentScene -> {
- scenePicker.contentDuringTransition(
- element = element,
- transition = transition,
- fromContentZIndex = layoutImpl.scene(transition.fromScene).zIndex,
- toContentZIndex = layoutImpl.scene(transition.toScene).zIndex,
- )
- }
- }
+ scenePicker.contentDuringTransition(
+ element = element,
+ transition = transition,
+ fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
+ toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
+ )
return pickedScene == content
}
@@ -1077,7 +1185,7 @@ private inline fun <T> computeValue(
val currentContent = currentContentState.content
if (transition is TransitionState.HasOverscrollProperties) {
val overscroll = transition.currentOverscrollSpec
- if (overscroll?.scene == currentContent) {
+ if (overscroll?.content == currentContent) {
val elementSpec =
overscroll.transformationSpec.transformations(element.key, currentContent)
val propertySpec = transformation(elementSpec) ?: return currentValue()
@@ -1103,7 +1211,7 @@ private inline fun <T> computeValue(
// TODO(b/290184746): Make sure that we don't overflow transformations associated to a
// range.
val directionSign = if (transition.isUpOrLeft) -1 else 1
- val isToContent = overscroll.scene == transition.toContent
+ val isToContent = overscroll.content == transition.toContent
val linearProgress = transition.progress.let { if (isToContent) it - 1f else it }
val progressConverter =
overscroll.progressConverter
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index acb436e4874b..3f8f5e742079 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -63,6 +63,18 @@ class SceneKey(
}
}
+/** Key for an overlay. */
+class OverlayKey(
+ debugName: String,
+ identity: Any = Object(),
+) : ContentKey(debugName, identity) {
+ override val testTag: String = "overlay:$debugName"
+
+ override fun toString(): String {
+ return "OverlayKey(debugName=$debugName)"
+ }
+}
+
/** Key for an element. */
open class ElementKey(
debugName: String,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 63d51f9bbcf4..715222cfd9da 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -26,7 +26,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.util.fastLastOrNull
import com.android.compose.animation.scene.content.Content
import com.android.compose.animation.scene.content.state.TransitionState
@@ -58,6 +57,13 @@ internal fun MovableElement(
modifier: Modifier,
content: @Composable ElementScope<MovableElementContentScope>.() -> Unit,
) {
+ check(key.contentPicker.contents.contains(sceneOrOverlay.key)) {
+ val elementName = key.debugName
+ val contentName = sceneOrOverlay.key.debugName
+ "MovableElement $elementName was composed in content $contentName but the " +
+ "MovableElementKey($elementName).contentPicker.contents does not contain $contentName"
+ }
+
Box(modifier.element(layoutImpl, sceneOrOverlay, key)) {
val contentScope = sceneOrOverlay.scope
val boxScope = this
@@ -153,13 +159,20 @@ private class MovableElementScopeImpl(
// size* as its movable content, i.e. the same *size when idle*. During transitions,
// this size will be used to interpolate the transition size, during the intermediate
// layout pass.
+ //
+ // Important: Like in Modifier.element(), we read the transition states during
+ // composition then pass them to Layout to make sure that composition sees new states
+ // before layout and drawing.
+ val transitionStates = layoutImpl.state.transitionStates
Layout { _, _ ->
// No need to measure or place anything.
val size =
placeholderContentSize(
- layoutImpl,
- contentKey,
- layoutImpl.elements.getValue(element),
+ layoutImpl = layoutImpl,
+ content = contentKey,
+ element = layoutImpl.elements.getValue(element),
+ elementKey = element,
+ transitionStates = transitionStates,
)
layout(size.width, size.height) {}
}
@@ -172,28 +185,43 @@ private fun shouldComposeMovableElement(
content: ContentKey,
element: MovableElementKey,
): Boolean {
- val transitions = layoutImpl.state.currentTransitions
- if (transitions.isEmpty()) {
- // If we are idle, there is only one [scene] that is composed so we can compose our
- // movable content here. We still check that [scene] is equal to the current idle scene, to
- // make sure we only compose it there.
- return layoutImpl.state.transitionState.currentScene == content
+ return when (
+ val elementState = movableElementState(element, layoutImpl.state.transitionStates)
+ ) {
+ null -> false
+ is TransitionState.Idle ->
+ movableElementContentWhenIdle(layoutImpl, element, elementState) == content
+ is TransitionState.Transition -> {
+ // During transitions, always compose movable elements in the scene picked by their
+ // content picker.
+ shouldPlaceOrComposeSharedElement(
+ layoutImpl,
+ content,
+ element,
+ elementState,
+ )
+ }
}
+}
- // The current transition for this element is the last transition in which either fromScene or
- // toScene contains the element.
- val contents = element.contentPicker.contents
- val transition =
- transitions.fastLastOrNull { transition ->
- transition.fromContent in contents || transition.toContent in contents
- } ?: return false
+private fun movableElementState(
+ element: MovableElementKey,
+ transitionStates: List<TransitionState>,
+): TransitionState? {
+ val content = element.contentPicker.contents
+ return elementState(transitionStates, isInContent = { content.contains(it) })
+}
- // Always compose movable elements in the scene picked by their scene picker.
- return shouldPlaceOrComposeSharedElement(
+private fun movableElementContentWhenIdle(
+ layoutImpl: SceneTransitionLayoutImpl,
+ element: MovableElementKey,
+ elementState: TransitionState.Idle,
+): ContentKey {
+ val contents = element.contentPicker.contents
+ return elementContentWhenIdle(
layoutImpl,
- content,
- element,
- transition,
+ elementState,
+ isInContent = { contents.contains(it) },
)
}
@@ -205,6 +233,8 @@ private fun placeholderContentSize(
layoutImpl: SceneTransitionLayoutImpl,
content: ContentKey,
element: Element,
+ elementKey: MovableElementKey,
+ transitionStates: List<TransitionState>,
): IntSize {
// If the content of the movable element was already composed in this scene before, use that
// target size.
@@ -213,20 +243,21 @@ private fun placeholderContentSize(
return targetValueInScene
}
- // This code is only run during transitions (otherwise the content would be composed and the
- // placeholder would not), so it's ok to cast the state into a Transition directly.
- val transition =
- layoutImpl.state.transitionState as TransitionState.Transition.ChangeCurrentScene
-
- // If the content was already composed in the other scene, we use that target size assuming it
- // doesn't change between scenes.
- // TODO(b/317026105): Provide a way to give a hint size/content for cases where this is not
- // true.
- val otherScene =
- if (transition.fromScene == content) transition.toScene else transition.fromScene
- val targetValueInOtherScene = element.stateByContent[otherScene]?.targetSize
- if (targetValueInOtherScene != null && targetValueInOtherScene != Element.SizeUnspecified) {
- return targetValueInOtherScene
+ // If the element content was already composed in the other overlay/scene, we use that
+ // target size assuming it doesn't change between scenes.
+ // TODO(b/317026105): Provide a way to give a hint size/content for cases where this is
+ // not true.
+ val otherContent =
+ when (val state = movableElementState(elementKey, transitionStates)) {
+ null -> return IntSize.Zero
+ is TransitionState.Idle -> movableElementContentWhenIdle(layoutImpl, elementKey, state)
+ is TransitionState.Transition ->
+ if (state.fromContent == content) state.toContent else state.fromContent
+ }
+
+ val targetValueInOtherContent = element.stateByContent[otherContent]?.targetSize
+ if (targetValueInOtherContent != null && targetValueInOtherContent != Element.SizeUnspecified) {
+ return targetValueInOtherContent
}
return IntSize.Zero
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 5071a7f744dc..236e202749b2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -43,7 +43,9 @@ sealed interface ObservableTransitionState {
fun currentScene(): Flow<SceneKey> {
return when (this) {
is Idle -> flowOf(currentScene)
- is Transition -> currentScene
+ is Transition.ChangeCurrentScene -> currentScene
+ is Transition.ShowOrHideOverlay -> flowOf(currentScene)
+ is Transition.ReplaceOverlay -> flowOf(currentScene)
}
}
@@ -51,10 +53,11 @@ sealed interface ObservableTransitionState {
data class Idle(val currentScene: SceneKey) : ObservableTransitionState
/** There is a transition animating between two scenes. */
- class Transition(
- val fromScene: SceneKey,
- val toScene: SceneKey,
- val currentScene: Flow<SceneKey>,
+ sealed class Transition(
+ // TODO(b/353679003): Rename these to fromContent and toContent.
+ open val fromScene: ContentKey,
+ open val toScene: ContentKey,
+ val currentOverlays: Flow<Set<OverlayKey>>,
val progress: Flow<Float>,
/**
@@ -76,10 +79,10 @@ sealed interface ObservableTransitionState {
val isUserInputOngoing: Flow<Boolean>,
/** Current progress of the preview part of the transition */
- val previewProgress: Flow<Float> = flowOf(0f),
+ val previewProgress: Flow<Float>,
/** Whether the transition is currently in the preview stage or not */
- val isInPreviewStage: Flow<Boolean> = flowOf(false),
+ val isInPreviewStage: Flow<Boolean>,
) : ObservableTransitionState {
override fun toString(): String =
"""Transition
@@ -89,13 +92,109 @@ sealed interface ObservableTransitionState {
| isUserInputOngoing=$isUserInputOngoing
|)"""
.trimMargin()
+
+ /** A transition animating between [fromScene] and [toScene]. */
+ class ChangeCurrentScene(
+ override val fromScene: SceneKey,
+ override val toScene: SceneKey,
+ val currentScene: Flow<SceneKey>,
+ currentOverlays: Flow<Set<OverlayKey>>,
+ progress: Flow<Float>,
+ isInitiatedByUserInput: Boolean,
+ isUserInputOngoing: Flow<Boolean>,
+ previewProgress: Flow<Float>,
+ isInPreviewStage: Flow<Boolean>,
+ ) :
+ Transition(
+ fromScene,
+ toScene,
+ currentOverlays,
+ progress,
+ isInitiatedByUserInput,
+ isUserInputOngoing,
+ previewProgress,
+ isInPreviewStage,
+ )
+
+ /** The [overlay] is either showing from [currentScene] or hiding into [currentScene]. */
+ class ShowOrHideOverlay(
+ val overlay: OverlayKey,
+ fromContent: ContentKey,
+ toContent: ContentKey,
+ val currentScene: SceneKey,
+ currentOverlays: Flow<Set<OverlayKey>>,
+ progress: Flow<Float>,
+ isInitiatedByUserInput: Boolean,
+ isUserInputOngoing: Flow<Boolean>,
+ previewProgress: Flow<Float>,
+ isInPreviewStage: Flow<Boolean>,
+ ) :
+ Transition(
+ fromContent,
+ toContent,
+ currentOverlays,
+ progress,
+ isInitiatedByUserInput,
+ isUserInputOngoing,
+ previewProgress,
+ isInPreviewStage,
+ )
+
+ /** We are transitioning from [fromOverlay] to [toOverlay]. */
+ class ReplaceOverlay(
+ val fromOverlay: OverlayKey,
+ val toOverlay: OverlayKey,
+ val currentScene: SceneKey,
+ currentOverlays: Flow<Set<OverlayKey>>,
+ progress: Flow<Float>,
+ isInitiatedByUserInput: Boolean,
+ isUserInputOngoing: Flow<Boolean>,
+ previewProgress: Flow<Float>,
+ isInPreviewStage: Flow<Boolean>,
+ ) :
+ Transition(
+ fromOverlay,
+ toOverlay,
+ currentOverlays,
+ progress,
+ isInitiatedByUserInput,
+ isUserInputOngoing,
+ previewProgress,
+ isInPreviewStage,
+ )
+
+ companion object {
+ operator fun invoke(
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ currentScene: Flow<SceneKey>,
+ progress: Flow<Float>,
+ isInitiatedByUserInput: Boolean,
+ isUserInputOngoing: Flow<Boolean>,
+ previewProgress: Flow<Float> = flowOf(0f),
+ isInPreviewStage: Flow<Boolean> = flowOf(false),
+ currentOverlays: Flow<Set<OverlayKey>> = flowOf(emptySet()),
+ ): ChangeCurrentScene {
+ return ChangeCurrentScene(
+ fromScene,
+ toScene,
+ currentScene,
+ currentOverlays,
+ progress,
+ isInitiatedByUserInput,
+ isUserInputOngoing,
+ previewProgress,
+ isInPreviewStage,
+ )
+ }
+ }
}
fun isIdle(scene: SceneKey?): Boolean {
return this is Idle && (scene == null || this.currentScene == scene)
}
- fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
+ fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean {
return this is Transition &&
(from == null || this.fromScene == from) &&
(to == null || this.toScene == to)
@@ -112,15 +211,44 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans
when (val state = transitionState) {
is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
is TransitionState.Transition.ChangeCurrentScene -> {
- ObservableTransitionState.Transition(
+ ObservableTransitionState.Transition.ChangeCurrentScene(
fromScene = state.fromScene,
toScene = state.toScene,
currentScene = snapshotFlow { state.currentScene },
+ currentOverlays = flowOf(state.currentOverlays),
+ progress = snapshotFlow { state.progress },
+ isInitiatedByUserInput = state.isInitiatedByUserInput,
+ isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+ previewProgress = snapshotFlow { state.previewProgress },
+ isInPreviewStage = snapshotFlow { state.isInPreviewStage },
+ )
+ }
+ is TransitionState.Transition.ShowOrHideOverlay -> {
+ check(state.fromOrToScene == state.currentScene)
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = state.overlay,
+ fromContent = state.fromContent,
+ toContent = state.toContent,
+ currentScene = state.currentScene,
+ currentOverlays = snapshotFlow { state.currentOverlays },
+ progress = snapshotFlow { state.progress },
+ isInitiatedByUserInput = state.isInitiatedByUserInput,
+ isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+ previewProgress = snapshotFlow { state.previewProgress },
+ isInPreviewStage = snapshotFlow { state.isInPreviewStage },
+ )
+ }
+ is TransitionState.Transition.ReplaceOverlay -> {
+ ObservableTransitionState.Transition.ReplaceOverlay(
+ fromOverlay = state.fromOverlay,
+ toOverlay = state.toOverlay,
+ currentScene = state.currentScene,
+ currentOverlays = snapshotFlow { state.currentOverlays },
progress = snapshotFlow { state.progress },
isInitiatedByUserInput = state.isInitiatedByUserInput,
isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
previewProgress = snapshotFlow { state.previewProgress },
- isInPreviewStage = snapshotFlow { state.isInPreviewStage }
+ isInPreviewStage = snapshotFlow { state.isInPreviewStage },
)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 65a73676d398..aaa2546b1d4b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -49,7 +49,7 @@ import androidx.compose.ui.unit.LayoutDirection
* if any.
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
- * @param scenes the configuration of the different scenes of this layout.
+ * @param builder the configuration of the different scenes and overlays of this layout.
*/
@Composable
fun SceneTransitionLayout(
@@ -58,7 +58,7 @@ fun SceneTransitionLayout(
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
- scenes: SceneTransitionLayoutScope.() -> Unit,
+ builder: SceneTransitionLayoutScope.() -> Unit,
) {
SceneTransitionLayoutForTesting(
state,
@@ -67,7 +67,7 @@ fun SceneTransitionLayout(
swipeDetector,
transitionInterceptionThreshold,
onLayoutImpl = null,
- scenes,
+ builder,
)
}
@@ -86,6 +86,31 @@ interface SceneTransitionLayoutScope {
userActions: Map<UserAction, UserActionResult> = emptyMap(),
content: @Composable ContentScope.() -> Unit,
)
+
+ /**
+ * Add an overlay to this layout, identified by [key].
+ *
+ * Overlays are displayed above scenes and can be toggled using
+ * [MutableSceneTransitionLayoutState.showOverlay] and
+ * [MutableSceneTransitionLayoutState.hideOverlay].
+ *
+ * Overlays will have a maximum size that is the size of the layout without overlays, i.e. an
+ * overlay can be fillMaxSize() to match the layout size but it won't make the layout bigger.
+ *
+ * By default overlays are centered in their layout but they can be aligned differently using
+ * [alignment].
+ *
+ * Important: overlays must be defined after all scenes. Overlay order along the z-axis follows
+ * call order. Calling overlay(A) followed by overlay(B) will mean that overlay B renders
+ * after/above overlay A.
+ */
+ // TODO(b/353679003): Allow to specify user actions. When overlays are shown, the user actions
+ // of the top-most overlay in currentOverlays will be used.
+ fun overlay(
+ key: OverlayKey,
+ alignment: Alignment = Alignment.Center,
+ content: @Composable ContentScope.() -> Unit,
+ )
}
/**
@@ -239,7 +264,7 @@ interface ContentScope : BaseContentScope {
/**
* Animate some value at the content level.
*
- * @param value the value of this shared value in the current scene.
+ * @param value the value of this shared value in the current content.
* @param key the key of this shared value.
* @param type the [SharedValueType] of this animated value.
* @param canOverflow whether this value can overflow past the values it is interpolated
@@ -292,7 +317,7 @@ interface ElementScope<ContentScope> {
/**
* Animate some value associated to this element.
*
- * @param value the value of this shared value in the current scene.
+ * @param value the value of this shared value in the current content.
* @param key the key of this shared value.
* @param type the [SharedValueType] of this animated value.
* @param canOverflow whether this value can overflow past the values it is interpolated
@@ -509,7 +534,7 @@ internal fun SceneTransitionLayoutForTesting(
swipeDetector: SwipeDetector = DefaultSwipeDetector,
transitionInterceptionThreshold: Float = 0f,
onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
- scenes: SceneTransitionLayoutScope.() -> Unit,
+ builder: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
@@ -521,7 +546,7 @@ internal fun SceneTransitionLayoutForTesting(
layoutDirection = layoutDirection,
swipeSourceDetector = swipeSourceDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
- builder = scenes,
+ builder = builder,
coroutineScope = coroutineScope,
)
.also { onLayoutImpl?.invoke(it) }
@@ -529,7 +554,7 @@ internal fun SceneTransitionLayoutForTesting(
// TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
// SnapshotStateMap anymore.
- layoutImpl.updateScenes(scenes, layoutDirection)
+ layoutImpl.updateContents(builder, layoutDirection)
SideEffect {
if (state != layoutImpl.state) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 392ff7ebb446..5f5141e1f153 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -18,10 +18,12 @@ package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.key
import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ApproachLayoutModifierNode
@@ -36,7 +38,9 @@ import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachReversed
+import androidx.compose.ui.zIndex
import com.android.compose.animation.scene.content.Content
+import com.android.compose.animation.scene.content.Overlay
import com.android.compose.animation.scene.content.Scene
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.ui.util.lerp
@@ -60,7 +64,17 @@ internal class SceneTransitionLayoutImpl(
*
* TODO(b/317014852): Make this a normal MutableMap instead.
*/
- internal val scenes = SnapshotStateMap<SceneKey, Scene>()
+ private val scenes = SnapshotStateMap<SceneKey, Scene>()
+
+ /**
+ * The map of [Overlays].
+ *
+ * Note: We lazily create this map to avoid instantiation an expensive SnapshotStateMap in the
+ * common case where there is no overlay in this layout.
+ */
+ private var _overlays: MutableMap<OverlayKey, Overlay>? = null
+ private val overlays
+ get() = _overlays ?: SnapshotStateMap<OverlayKey, Overlay>().also { _overlays = it }
/**
* The map of [Element]s.
@@ -119,7 +133,7 @@ internal class SceneTransitionLayoutImpl(
private set
init {
- updateScenes(builder, layoutDirection)
+ updateContents(builder, layoutDirection)
// DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
// current scene (required for SwipeTransition).
@@ -152,22 +166,32 @@ internal class SceneTransitionLayoutImpl(
return scenes[key] ?: error("Scene $key is not configured")
}
+ internal fun sceneOrNull(key: SceneKey): Scene? = scenes[key]
+
+ internal fun overlay(key: OverlayKey): Overlay {
+ return overlays[key] ?: error("Overlay $key is not configured")
+ }
+
internal fun content(key: ContentKey): Content {
return when (key) {
is SceneKey -> scene(key)
+ is OverlayKey -> overlay(key)
}
}
- internal fun updateScenes(
+ internal fun updateContents(
builder: SceneTransitionLayoutScope.() -> Unit,
layoutDirection: LayoutDirection,
) {
- // Keep a reference of the current scenes. After processing [builder], the scenes that were
- // not configured will be removed.
+ // Keep a reference of the current contents. After processing [builder], the contents that
+ // were not configured will be removed.
val scenesToRemove = scenes.keys.toMutableSet()
+ val overlaysToRemove =
+ if (_overlays == null) mutableSetOf() else overlays.keys.toMutableSet()
// The incrementing zIndex of each scene.
var zIndex = 0f
+ var overlaysDefined = false
object : SceneTransitionLayoutScope {
override fun scene(
@@ -175,6 +199,8 @@ internal class SceneTransitionLayoutImpl(
userActions: Map<UserAction, UserActionResult>,
content: @Composable ContentScope.() -> Unit,
) {
+ require(!overlaysDefined) { "all scenes must be defined before overlays" }
+
scenesToRemove.remove(key)
val resolvedUserActions =
@@ -199,10 +225,42 @@ internal class SceneTransitionLayoutImpl(
zIndex++
}
+
+ override fun overlay(
+ key: OverlayKey,
+ alignment: Alignment,
+ content: @Composable (ContentScope.() -> Unit)
+ ) {
+ overlaysDefined = true
+ overlaysToRemove.remove(key)
+
+ val overlay = overlays[key]
+ if (overlay != null) {
+ // Update an existing overlay.
+ overlay.content = content
+ overlay.zIndex = zIndex
+ overlay.alignment = alignment
+ } else {
+ // New overlay.
+ overlays[key] =
+ Overlay(
+ key,
+ this@SceneTransitionLayoutImpl,
+ content,
+ // TODO(b/353679003): Allow to specify user actions
+ actions = emptyMap(),
+ zIndex,
+ alignment,
+ )
+ }
+
+ zIndex++
+ }
}
.builder()
scenesToRemove.forEach { scenes.remove(it) }
+ overlaysToRemove.forEach { overlays.remove(it) }
}
@Composable
@@ -220,8 +278,8 @@ internal class SceneTransitionLayoutImpl(
lookaheadScope = this
BackHandler()
-
- scenesToCompose().fastForEach { scene -> key(scene.key) { scene.Content() } }
+ Scenes()
+ Overlays()
}
}
}
@@ -233,6 +291,11 @@ internal class SceneTransitionLayoutImpl(
PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
}
+ @Composable
+ private fun Scenes() {
+ scenesToCompose().fastForEach { scene -> key(scene.key) { scene.Content() } }
+ }
+
private fun scenesToCompose(): List<Scene> {
val transitions = state.currentTransitions
return if (transitions.isEmpty()) {
@@ -253,15 +316,74 @@ internal class SceneTransitionLayoutImpl(
maybeAdd(transition.toScene)
maybeAdd(transition.fromScene)
}
+ is TransitionState.Transition.ShowOrHideOverlay ->
+ maybeAdd(transition.fromOrToScene)
+ is TransitionState.Transition.ReplaceOverlay -> {}
}
}
+
+ // Make sure that the current scene is always composed.
+ maybeAdd(transitions.last().currentScene)
+ }
+ }
+ }
+
+ @Composable
+ private fun BoxScope.Overlays() {
+ val overlaysOrderedByZIndex = overlaysToComposeOrderedByZIndex()
+ if (overlaysOrderedByZIndex.isEmpty()) {
+ return
+ }
+
+ // We put the overlays inside a Box that is matching the layout size so that overlays are
+ // measured after all scenes and that their max size is the size of the layout without the
+ // overlays.
+ Box(Modifier.matchParentSize().zIndex(overlaysOrderedByZIndex.first().zIndex)) {
+ overlaysOrderedByZIndex.fastForEach { overlay ->
+ key(overlay.key) { overlay.Content(Modifier.align(overlay.alignment)) }
}
}
}
+ private fun overlaysToComposeOrderedByZIndex(): List<Overlay> {
+ if (_overlays == null) return emptyList()
+
+ val transitions = state.currentTransitions
+ return if (transitions.isEmpty()) {
+ state.transitionState.currentOverlays.map { overlay(it) }
+ } else {
+ buildList {
+ val visited = mutableSetOf<OverlayKey>()
+ fun maybeAdd(key: OverlayKey) {
+ if (visited.add(key)) {
+ add(overlay(key))
+ }
+ }
+
+ transitions.fastForEach { transition ->
+ when (transition) {
+ is TransitionState.Transition.ChangeCurrentScene -> {}
+ is TransitionState.Transition.ShowOrHideOverlay ->
+ maybeAdd(transition.overlay)
+ is TransitionState.Transition.ReplaceOverlay -> {
+ maybeAdd(transition.fromOverlay)
+ maybeAdd(transition.toOverlay)
+ }
+ }
+ }
+
+ // Make sure that all current overlays are composed.
+ transitions.last().currentOverlays.forEach { maybeAdd(it) }
+ }
+ }
+ .sortedBy { it.zIndex }
+ }
+
internal fun setScenesTargetSizeForTest(size: IntSize) {
scenes.values.forEach { it.targetSize = size }
}
+
+ internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays
}
private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutImpl) :
@@ -310,7 +432,7 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
val progress =
when {
overscrollSpec == null -> transition.progress
- overscrollSpec.scene == transition.toScene -> 1f
+ overscrollSpec.content == transition.toScene -> 1f
else -> 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index f37ded0547ea..0ac69124f7bc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -39,6 +39,21 @@ import kotlinx.coroutines.CoroutineScope
@Stable
sealed interface SceneTransitionLayoutState {
/**
+ * The current effective scene. If a new transition is triggered, it will start from this scene.
+ */
+ val currentScene: SceneKey
+
+ /**
+ * The current set of overlays. This represents the set of overlays that will be visible on
+ * screen once all [currentTransitions] are finished.
+ *
+ * @see MutableSceneTransitionLayoutState.showOverlay
+ * @see MutableSceneTransitionLayoutState.hideOverlay
+ * @see MutableSceneTransitionLayoutState.replaceOverlay
+ */
+ val currentOverlays: Set<OverlayKey>
+
+ /**
* The current [TransitionState]. All values read here are backed by the Snapshot system.
*
* To observe those values outside of Compose/the Snapshot system, use
@@ -66,12 +81,15 @@ sealed interface SceneTransitionLayoutState {
/**
* Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
- * the scenes we are animating from and/or to.
+ * the contents we are animating from and/or to.
*/
- fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean
+ fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean
+
+ /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
+ fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean
- /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
- fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean
+ /** Whether we are transitioning from or to [content]. */
+ fun isTransitioningFromOrTo(content: ContentKey): Boolean
}
/** A [SceneTransitionLayoutState] whose target scene can be imperatively set. */
@@ -110,7 +128,50 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState
): TransitionState.Transition?
/** Immediately snap to the given [scene]. */
- fun snapToScene(scene: SceneKey)
+ fun snapToScene(
+ scene: SceneKey,
+ currentOverlays: Set<OverlayKey> = transitionState.currentOverlays,
+ )
+
+ /**
+ * Request to show [overlay] so that it animates in from [currentScene] and ends up being
+ * visible on screen.
+ *
+ * After this returns, this overlay will be included in [currentOverlays]. This does nothing if
+ * [overlay] is already in [currentOverlays].
+ */
+ fun showOverlay(
+ overlay: OverlayKey,
+ animationScope: CoroutineScope,
+ transitionKey: TransitionKey? = null,
+ )
+
+ /**
+ * Request to hide [overlay] so that it animates out to [currentScene] and ends up *not* being
+ * visible on screen.
+ *
+ * After this returns, this overlay will not be included in [currentOverlays]. This does nothing
+ * if [overlay] is not in [currentOverlays].
+ */
+ fun hideOverlay(
+ overlay: OverlayKey,
+ animationScope: CoroutineScope,
+ transitionKey: TransitionKey? = null,
+ )
+
+ /**
+ * Replace [from] by [to] so that [from] ends up not being visible on screen and [to] ends up
+ * being visible.
+ *
+ * This throws if [from] is not currently in [currentOverlays] or if [to] is already in
+ * [currentOverlays].
+ */
+ fun replaceOverlay(
+ from: OverlayKey,
+ to: OverlayKey,
+ animationScope: CoroutineScope,
+ transitionKey: TransitionKey? = null,
+ )
}
/**
@@ -128,6 +189,7 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
+ initialOverlays: Set<OverlayKey> = emptySet(),
canChangeScene: (SceneKey) -> Boolean = { true },
stateLinks: List<StateLink> = emptyList(),
enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
@@ -135,6 +197,7 @@ fun MutableSceneTransitionLayoutState(
return MutableSceneTransitionLayoutStateImpl(
initialScene,
transitions,
+ initialOverlays,
canChangeScene,
stateLinks,
enableInterruptions,
@@ -145,6 +208,7 @@ fun MutableSceneTransitionLayoutState(
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
override var transitions: SceneTransitions = transitions {},
+ initialOverlays: Set<OverlayKey> = emptySet(),
internal val canChangeScene: (SceneKey) -> Boolean = { true },
private val stateLinks: List<StateLink> = emptyList(),
@@ -158,13 +222,18 @@ internal class MutableSceneTransitionLayoutStateImpl(
* 1. A list with a single [TransitionState.Idle] element, when we are idle.
* 2. A list with one or more [TransitionState.Transition], when we are transitioning.
*/
- @VisibleForTesting
internal var transitionStates: List<TransitionState> by
- mutableStateOf(listOf(TransitionState.Idle(initialScene)))
+ mutableStateOf(listOf(TransitionState.Idle(initialScene, initialOverlays)))
private set
+ override val currentScene: SceneKey
+ get() = transitionState.currentScene
+
+ override val currentOverlays: Set<OverlayKey>
+ get() = transitionState.currentOverlays
+
override val transitionState: TransitionState
- get() = transitionStates.last()
+ get() = transitionStates[transitionStates.lastIndex]
override val currentTransitions: List<TransitionState.Transition>
get() {
@@ -194,14 +263,19 @@ internal class MutableSceneTransitionLayoutStateImpl(
}
}
- override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
+ override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean {
val transition = currentTransition ?: return false
return transition.isTransitioning(from, to)
}
- override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+ override fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
+ val transition = currentTransition ?: return false
+ return transition.isTransitioningBetween(content, other)
+ }
+
+ override fun isTransitioningFromOrTo(content: ContentKey): Boolean {
val transition = currentTransition ?: return false
- return transition.isTransitioningBetween(scene, other)
+ return transition.isTransitioningFromOrTo(content)
}
override fun setTargetScene(
@@ -227,30 +301,32 @@ internal class MutableSceneTransitionLayoutStateImpl(
*
* Important: you *must* call [finishTransition] once the transition is finished.
*/
- internal fun startTransition(
- transition: TransitionState.Transition.ChangeCurrentScene,
- chain: Boolean = true,
- ) {
+ internal fun startTransition(transition: TransitionState.Transition, chain: Boolean = true) {
checkThread()
+ // Set the current scene and overlays on the transition.
+ val currentState = transitionState
+ transition.currentSceneWhenTransitionStarted = currentState.currentScene
+ transition.currentOverlaysWhenTransitionStarted = currentState.currentOverlays
+
// Compute the [TransformationSpec] when the transition starts.
- val fromScene = transition.fromScene
- val toScene = transition.toScene
+ val fromContent = transition.fromContent
+ val toContent = transition.toContent
val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
// Update the transition specs.
transition.transformationSpec =
transitions
- .transitionSpec(fromScene, toScene, key = transition.key)
+ .transitionSpec(fromContent, toContent, key = transition.key)
.transformationSpec()
transition.previewTransformationSpec =
transitions
- .transitionSpec(fromScene, toScene, key = transition.key)
+ .transitionSpec(fromContent, toContent, key = transition.key)
.previewTransformationSpec()
if (orientation != null) {
transition.updateOverscrollSpecs(
- fromSpec = transitions.overscrollSpec(fromScene, orientation),
- toSpec = transitions.overscrollSpec(toScene, orientation),
+ fromSpec = transitions.overscrollSpec(fromContent, orientation),
+ toSpec = transitions.overscrollSpec(toContent, orientation),
)
} else {
transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
@@ -331,31 +407,27 @@ internal class MutableSceneTransitionLayoutStateImpl(
}
private fun setupTransitionLinks(transition: TransitionState.Transition) {
- when (transition) {
- is TransitionState.Transition.ChangeCurrentScene -> {
- stateLinks.fastForEach { stateLink ->
- val matchingLinks =
- stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
- if (matchingLinks.isEmpty()) return@fastForEach
- if (matchingLinks.size > 1) error("More than one link matched.")
-
- val targetCurrentScene = stateLink.target.transitionState.currentScene
- val matchingLink = matchingLinks[0]
-
- if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
-
- val linkedTransition =
- LinkedTransition(
- originalTransition = transition,
- fromScene = targetCurrentScene,
- toScene = matchingLink.targetTo,
- key = matchingLink.targetTransitionKey,
- )
-
- stateLink.target.startTransition(linkedTransition)
- transition.activeTransitionLinks[stateLink] = linkedTransition
- }
- }
+ stateLinks.fastForEach { stateLink ->
+ val matchingLinks =
+ stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
+ if (matchingLinks.isEmpty()) return@fastForEach
+ if (matchingLinks.size > 1) error("More than one link matched.")
+
+ val targetCurrentScene = stateLink.target.transitionState.currentScene
+ val matchingLink = matchingLinks[0]
+
+ if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+
+ val linkedTransition =
+ LinkedTransition(
+ originalTransition = transition,
+ fromScene = targetCurrentScene,
+ toScene = matchingLink.targetTo,
+ key = matchingLink.targetTransitionKey,
+ )
+
+ stateLink.target.startTransition(linkedTransition)
+ transition.activeTransitionLinks[stateLink] = linkedTransition
}
}
@@ -408,23 +480,28 @@ internal class MutableSceneTransitionLayoutStateImpl(
// If all transitions are finished, we are idle.
if (i == nStates) {
check(finishedTransitions.isEmpty())
- this.transitionStates = listOf(TransitionState.Idle(lastTransition.currentScene))
+ this.transitionStates =
+ listOf(
+ TransitionState.Idle(
+ lastTransition.currentScene,
+ lastTransition.currentOverlays,
+ )
+ )
} else if (i > 0) {
this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates)
}
}
- override fun snapToScene(scene: SceneKey) {
+ override fun snapToScene(scene: SceneKey, currentOverlays: Set<OverlayKey>) {
checkThread()
// Force finish all transitions.
while (currentTransitions.isNotEmpty()) {
- val transition = transitionStates[0] as TransitionState.Transition
- finishTransition(transition)
+ finishTransition(transitionStates[0] as TransitionState.Transition)
}
check(transitionStates.size == 1)
- transitionStates = listOf(TransitionState.Idle(scene))
+ transitionStates = listOf(TransitionState.Idle(scene, currentOverlays))
}
private fun finishActiveTransitionLinks(transition: TransitionState.Transition) {
@@ -466,6 +543,137 @@ internal class MutableSceneTransitionLayoutStateImpl(
false
}
}
+
+ override fun showOverlay(
+ overlay: OverlayKey,
+ animationScope: CoroutineScope,
+ transitionKey: TransitionKey?
+ ) {
+ checkThread()
+
+ // Overlay is already shown, do nothing.
+ val currentState = transitionState
+ if (overlay in currentState.currentOverlays) {
+ return
+ }
+
+ val fromScene = currentState.currentScene
+ fun animate(
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.showOrHideOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ overlay = overlay,
+ fromOrToScene = fromScene,
+ isShowing = true,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (
+ currentState is TransitionState.Transition.ShowOrHideOverlay &&
+ currentState.overlay == overlay &&
+ currentState.fromOrToScene == fromScene
+ ) {
+ animate(
+ replacedTransition = currentState,
+ reversed = overlay == currentState.fromContent
+ )
+ } else {
+ animate()
+ }
+ }
+
+ override fun hideOverlay(
+ overlay: OverlayKey,
+ animationScope: CoroutineScope,
+ transitionKey: TransitionKey?
+ ) {
+ checkThread()
+
+ // Overlay is not shown, do nothing.
+ val currentState = transitionState
+ if (!currentState.currentOverlays.contains(overlay)) {
+ return
+ }
+
+ val toScene = currentState.currentScene
+ fun animate(
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.showOrHideOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ overlay = overlay,
+ fromOrToScene = toScene,
+ isShowing = false,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (
+ currentState is TransitionState.Transition.ShowOrHideOverlay &&
+ currentState.overlay == overlay &&
+ currentState.fromOrToScene == toScene
+ ) {
+ animate(replacedTransition = currentState, reversed = overlay == currentState.toContent)
+ } else {
+ animate()
+ }
+ }
+
+ override fun replaceOverlay(
+ from: OverlayKey,
+ to: OverlayKey,
+ animationScope: CoroutineScope,
+ transitionKey: TransitionKey?
+ ) {
+ checkThread()
+
+ val currentState = transitionState
+ require(from != to) {
+ "replaceOverlay must be called with different overlays (from = to = ${from.debugName})"
+ }
+ require(from in currentState.currentOverlays) {
+ "Overlay ${from.debugName} is not shown so it can't be replaced by ${to.debugName}"
+ }
+ require(to !in currentState.currentOverlays) {
+ "Overlay ${to.debugName} is already shown so it can't replace ${from.debugName}"
+ }
+
+ fun animate(
+ replacedTransition: TransitionState.Transition.ReplaceOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.replaceOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ fromOverlay = if (reversed) to else from,
+ toOverlay = if (reversed) from else to,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (currentState is TransitionState.Transition.ReplaceOverlay) {
+ if (currentState.fromOverlay == from && currentState.toOverlay == to) {
+ animate(replacedTransition = currentState, reversed = false)
+ return
+ }
+
+ if (currentState.fromOverlay == to && currentState.toOverlay == from) {
+ animate(replacedTransition = currentState, reversed = true)
+ return
+ }
+ }
+
+ animate()
+ }
}
private const val TAG = "SceneTransitionLayoutState"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d35d95685d22..cefcff75f13a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -49,16 +49,16 @@ internal constructor(
) {
private val transitionCache =
mutableMapOf<
- SceneKey,
- MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
+ ContentKey,
+ MutableMap<ContentKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
>()
private val overscrollCache =
- mutableMapOf<SceneKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
+ mutableMapOf<ContentKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
internal fun transitionSpec(
- from: SceneKey,
- to: SceneKey,
+ from: ContentKey,
+ to: ContentKey,
key: TransitionKey?,
): TransitionSpecImpl {
return transitionCache
@@ -67,7 +67,11 @@ internal constructor(
.getOrPut(key) { findSpec(from, to, key) }
}
- private fun findSpec(from: SceneKey, to: SceneKey, key: TransitionKey?): TransitionSpecImpl {
+ private fun findSpec(
+ from: ContentKey,
+ to: ContentKey,
+ key: TransitionKey?
+ ): TransitionSpecImpl {
val spec = transition(from, to, key) { it.from == from && it.to == to }
if (spec != null) {
return spec
@@ -93,8 +97,8 @@ internal constructor(
}
private fun transition(
- from: SceneKey,
- to: SceneKey,
+ from: ContentKey,
+ to: ContentKey,
key: TransitionKey?,
filter: (TransitionSpecImpl) -> Boolean,
): TransitionSpecImpl? {
@@ -110,16 +114,16 @@ internal constructor(
return match
}
- private fun defaultTransition(from: SceneKey, to: SceneKey) =
+ private fun defaultTransition(from: ContentKey, to: ContentKey) =
TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider)
- internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
+ internal fun overscrollSpec(scene: ContentKey, orientation: Orientation): OverscrollSpecImpl? =
overscrollCache
.getOrPut(scene) { mutableMapOf() }
- .getOrPut(orientation) { overscroll(scene, orientation) { it.scene == scene } }
+ .getOrPut(orientation) { overscroll(scene, orientation) { it.content == scene } }
private fun overscroll(
- scene: SceneKey,
+ scene: ContentKey,
orientation: Orientation,
filter: (OverscrollSpecImpl) -> Boolean,
): OverscrollSpecImpl? {
@@ -264,10 +268,10 @@ internal class TransitionSpecImpl(
previewTransformationSpec?.invoke()
}
-/** The definition of the overscroll behavior of the [scene]. */
+/** The definition of the overscroll behavior of the [content]. */
interface OverscrollSpec {
/** The scene we are over scrolling. */
- val scene: SceneKey
+ val content: ContentKey
/** The orientation of this [OverscrollSpec]. */
val orientation: Orientation
@@ -288,7 +292,7 @@ interface OverscrollSpec {
}
internal class OverscrollSpecImpl(
- override val scene: SceneKey,
+ override val content: ContentKey,
override val orientation: Orientation,
override val transformationSpec: TransformationSpecImpl,
override val progressConverter: ProgressConverter?,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 5cc194d32424..2b5953c586db 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -523,8 +523,8 @@ fun interface ProgressConverter {
fun convert(progress: Float): Float
companion object {
- /** Keeps scrolling linearly */
- val Default = linear()
+ /** Starts linearly with some resistance and slowly approaches to 0.2f */
+ val Default = tanh(maxProgress = 0.2f, tilt = 3f)
/**
* The scroll stays linear, with [factor] you can control how much resistance there is.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 523e5bdd7203..18e356f71768 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -107,7 +107,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
): OverscrollSpec {
val spec =
OverscrollSpecImpl(
- scene = scene,
+ content = scene,
orientation = orientation,
transformationSpec =
TransformationSpecImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
index 0f668044112e..9851b32c42c4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
@@ -35,7 +35,7 @@ internal class ElementStateScopeImpl(
}
override fun SceneKey.targetSize(): IntSize? {
- return layoutImpl.scenes[this]?.targetSize.takeIf { it != IntSize.Zero }
+ return layoutImpl.sceneOrNull(this)?.targetSize.takeIf { it != IntSize.Zero }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 6f608cbc1d7f..6bc1754150fe 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -96,6 +96,8 @@ internal sealed class Content(
.approachLayout(
isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
) { measurable, constraints ->
+ // TODO(b/353679003): Use the ModifierNode API to set this *before* the approach
+ // pass is started.
targetSize = lookaheadSize
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt
new file mode 100644
index 000000000000..ccec9e834385
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.content
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+
+/** An overlay defined in a [SceneTransitionLayout]. */
+@Stable
+internal class Overlay(
+ override val key: OverlayKey,
+ layoutImpl: SceneTransitionLayoutImpl,
+ content: @Composable ContentScope.() -> Unit,
+ actions: Map<UserAction.Resolved, UserActionResult>,
+ zIndex: Float,
+ alignment: Alignment,
+) : Content(key, layoutImpl, content, actions, zIndex) {
+ var alignment by mutableStateOf(alignment)
+
+ override fun toString(): String {
+ return "Overlay(key=$key)"
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index 22df34b34b97..fdb019f5a604 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -21,7 +21,11 @@ import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.OverscrollScope
import com.android.compose.animation.scene.OverscrollSpecImpl
import com.android.compose.animation.scene.ProgressVisibilityThreshold
@@ -49,9 +53,20 @@ sealed interface TransitionState {
*/
val currentScene: SceneKey
+ /**
+ * The current set of overlays. This represents the set of overlays that will be visible on
+ * screen once all transitions are finished.
+ *
+ * @see MutableSceneTransitionLayoutState.showOverlay
+ * @see MutableSceneTransitionLayoutState.hideOverlay
+ * @see MutableSceneTransitionLayoutState.replaceOverlay
+ */
+ val currentOverlays: Set<OverlayKey>
+
/** The scene [currentScene] is idle. */
data class Idle(
override val currentScene: SceneKey,
+ override val currentOverlays: Set<OverlayKey> = emptySet(),
) : TransitionState
sealed class Transition(
@@ -69,7 +84,125 @@ sealed interface TransitionState {
/** The transition that `this` transition is replacing, if any. */
replacedTransition: Transition? = null,
- ) : Transition(fromScene, toScene, replacedTransition)
+ ) : Transition(fromScene, toScene, replacedTransition) {
+ final override val currentOverlays: Set<OverlayKey>
+ get() {
+ // The set of overlays does not change in a [ChangeCurrentScene] transition.
+ return currentOverlaysWhenTransitionStarted
+ }
+ }
+
+ /**
+ * A transition that is animating one or more overlays and for which [currentOverlays] will
+ * change over the course of the transition.
+ */
+ sealed class OverlayTransition(
+ fromContent: ContentKey,
+ toContent: ContentKey,
+ replacedTransition: Transition?,
+ ) : Transition(fromContent, toContent, replacedTransition) {
+ final override val currentScene: SceneKey
+ get() {
+ // The current scene does not change during overlay transitions.
+ return currentSceneWhenTransitionStarted
+ }
+
+ // Note: We use deriveStateOf() so that the computed set is cached and reused when the
+ // inputs of the computations don't change, to avoid recomputing and allocating a new
+ // set every time currentOverlays is called (which is every frame and for each element).
+ final override val currentOverlays: Set<OverlayKey> by derivedStateOf {
+ computeCurrentOverlays()
+ }
+
+ protected abstract fun computeCurrentOverlays(): Set<OverlayKey>
+ }
+
+ /** The [overlay] is either showing from [fromOrToScene] or hiding into [fromOrToScene]. */
+ abstract class ShowOrHideOverlay(
+ val overlay: OverlayKey,
+ val fromOrToScene: SceneKey,
+ fromContent: ContentKey,
+ toContent: ContentKey,
+ replacedTransition: Transition? = null,
+ ) : OverlayTransition(fromContent, toContent, replacedTransition) {
+ /**
+ * Whether [overlay] is effectively shown. For instance, this will be `false` when
+ * starting a swipe transition to show [overlay] and will be `true` only once the swipe
+ * transition is committed.
+ */
+ protected abstract val isEffectivelyShown: Boolean
+
+ init {
+ check(
+ (fromContent == fromOrToScene && toContent == overlay) ||
+ (fromContent == overlay && toContent == fromOrToScene)
+ )
+ }
+
+ final override fun computeCurrentOverlays(): Set<OverlayKey> {
+ return if (isEffectivelyShown) {
+ currentOverlaysWhenTransitionStarted + overlay
+ } else {
+ currentOverlaysWhenTransitionStarted - overlay
+ }
+ }
+ }
+
+ /** We are transitioning from [fromOverlay] to [toOverlay]. */
+ abstract class ReplaceOverlay(
+ val fromOverlay: OverlayKey,
+ val toOverlay: OverlayKey,
+ replacedTransition: Transition? = null,
+ ) :
+ OverlayTransition(
+ fromContent = fromOverlay,
+ toContent = toOverlay,
+ replacedTransition,
+ ) {
+ /**
+ * The current effective overlay, either [fromOverlay] or [toOverlay]. For instance,
+ * this will be [fromOverlay] when starting a swipe transition that replaces
+ * [fromOverlay] by [toOverlay] and will [toOverlay] once the swipe transition is
+ * committed.
+ */
+ protected abstract val effectivelyShownOverlay: OverlayKey
+
+ init {
+ check(fromOverlay != toOverlay)
+ }
+
+ final override fun computeCurrentOverlays(): Set<OverlayKey> {
+ return when (effectivelyShownOverlay) {
+ fromOverlay ->
+ computeCurrentOverlays(include = fromOverlay, exclude = toOverlay)
+ toOverlay -> computeCurrentOverlays(include = toOverlay, exclude = fromOverlay)
+ else ->
+ error(
+ "effectivelyShownOverlay=$effectivelyShownOverlay, should be " +
+ "equal to fromOverlay=$fromOverlay or toOverlay=$toOverlay"
+ )
+ }
+ }
+
+ private fun computeCurrentOverlays(
+ include: OverlayKey,
+ exclude: OverlayKey
+ ): Set<OverlayKey> {
+ return buildSet {
+ addAll(currentOverlaysWhenTransitionStarted)
+ remove(exclude)
+ add(include)
+ }
+ }
+ }
+
+ /**
+ * The current scene and overlays observed right when this transition started. These are set
+ * when this transition is started in
+ * [com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl.startTransition].
+ */
+ internal lateinit var currentSceneWhenTransitionStarted: SceneKey
+ internal lateinit var currentOverlaysWhenTransitionStarted: Set<OverlayKey>
/**
* The key of this transition. This should usually be null, but it can be specified to use a
@@ -163,6 +296,11 @@ sealed interface TransitionState {
isTransitioning(from = other, to = content)
}
+ /** Whether we are transitioning from or to [content]. */
+ fun isTransitioningFromOrTo(content: ContentKey): Boolean {
+ return fromContent == content || toContent == content
+ }
+
/**
* Force this transition to finish and animate to an [Idle] state.
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 89b004046475..59ddb1354073 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -23,7 +23,7 @@ import kotlinx.coroutines.Job
/** A linked transition which is driven by a [originalTransition]. */
internal class LinkedTransition(
- private val originalTransition: TransitionState.Transition.ChangeCurrentScene,
+ private val originalTransition: TransitionState.Transition,
fromScene: SceneKey,
toScene: SceneKey,
override val key: TransitionKey? = null,
@@ -32,8 +32,8 @@ internal class LinkedTransition(
override val currentScene: SceneKey
get() {
return when (originalTransition.currentScene) {
- originalTransition.fromScene -> fromScene
- originalTransition.toScene -> toScene
+ originalTransition.fromContent -> fromScene
+ originalTransition.toContent -> toScene
else -> error("Original currentScene is neither FromScene nor ToScene")
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
index c29bf212ec9c..c830ca4fa7c0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene.transition.link
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutState
@@ -35,8 +36,8 @@ class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<Tr
* target to `SceneA` from any current scene.
*/
class TransitionLink(
- val sourceFrom: SceneKey?,
- val sourceTo: SceneKey?,
+ val sourceFrom: ContentKey?,
+ val sourceTo: ContentKey?,
val targetFrom: SceneKey?,
val targetTo: SceneKey,
val targetTransitionKey: TransitionKey? = null,
@@ -50,15 +51,15 @@ class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<Tr
}
internal fun isMatchingLink(
- transition: TransitionState.Transition.ChangeCurrentScene,
+ transition: TransitionState.Transition,
): Boolean {
- return (sourceFrom == null || sourceFrom == transition.fromScene) &&
- (sourceTo == null || sourceTo == transition.toScene)
+ return (sourceFrom == null || sourceFrom == transition.fromContent) &&
+ (sourceTo == null || sourceTo == transition.toContent)
}
- internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
- return (targetFrom == null || targetFrom == targetCurrentScene) &&
- targetTo != targetCurrentScene
+ internal fun targetIsInValidState(targetCurrentContent: ContentKey): Boolean {
+ return (targetFrom == null || targetFrom == targetCurrentContent) &&
+ targetTo != targetCurrentContent
}
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 01895c91a399..8ebb42aa24f8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -168,10 +168,7 @@ class AnimatedSharedAsStateTest {
assertThat(lastValueInTo).isEqualTo(expectedValues)
}
- after {
- assertThat(lastValueInFrom).isEqualTo(toValues)
- assertThat(lastValueInTo).isEqualTo(toValues)
- }
+ after { assertThat(lastValueInTo).isEqualTo(toValues) }
}
}
@@ -229,10 +226,7 @@ class AnimatedSharedAsStateTest {
assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
}
- after {
- assertThat(lastValueInFrom).isEqualTo(toValues)
- assertThat(lastValueInTo).isEqualTo(toValues)
- }
+ after { assertThat(lastValueInTo).isEqualTo(toValues) }
}
}
@@ -288,10 +282,7 @@ class AnimatedSharedAsStateTest {
assertThat(lastValueInTo).isEqualTo(expectedValues)
}
- after {
- assertThat(lastValueInFrom).isEqualTo(toValues)
- assertThat(lastValueInTo).isEqualTo(toValues)
- }
+ after { assertThat(lastValueInTo).isEqualTo(toValues) }
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 72a16b7fbd6f..7d8e898e9ab2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -65,19 +65,19 @@ class DraggableHandlerTest {
var layoutDirection = LayoutDirection.Rtl
set(value) {
field = value
- layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ layoutImpl.updateContents(scenesBuilder, layoutDirection)
}
var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
set(value) {
field = value
- layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ layoutImpl.updateContents(scenesBuilder, layoutDirection)
}
var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
set(value) {
field = value
- layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ layoutImpl.updateContents(scenesBuilder, layoutDirection)
}
private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
@@ -1020,7 +1020,7 @@ class DraggableHandlerTest {
// We scrolled down, under scene C there is nothing, so we can use the overscroll spec
assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull()
- assertThat(layoutState.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneC)
+ assertThat(layoutState.currentTransition?.currentOverscrollSpec?.content).isEqualTo(SceneC)
val transition = layoutState.currentTransition
assertThat(transition).isNotNull()
assertThat(transition!!.progress).isEqualTo(-0.1f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 682fe95c66a5..770c0f8dbb8f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -724,6 +724,7 @@ class ElementTest {
layoutHeight = layoutHeight,
sceneTransitions = {
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
// On overscroll 100% -> Foo should translate by overscrollTranslateY
translate(TestElements.Foo, y = overscrollTranslateY)
}
@@ -780,6 +781,7 @@ class ElementTest {
transitions =
transitions {
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
translate(TestElements.Foo, y = overscrollTranslateY)
}
}
@@ -921,6 +923,7 @@ class ElementTest {
layoutHeight = layoutHeight,
sceneTransitions = {
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
// On overscroll 100% -> Foo should translate by layoutHeight
translate(TestElements.Foo, y = { absoluteDistance })
}
@@ -1015,7 +1018,7 @@ class ElementTest {
layoutHeight = layoutHeight,
sceneTransitions = {
// Overscroll progress will be linear (by default)
- defaultOverscrollProgressConverter = ProgressConverter { it }
+ defaultOverscrollProgressConverter = ProgressConverter.linear()
overscroll(SceneB, Orientation.Vertical) {
// This override the defaultOverscrollProgressConverter
@@ -1125,6 +1128,7 @@ class ElementTest {
)
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
// On overscroll 100% -> Foo should translate by layoutHeight
translate(TestElements.Foo, y = { absoluteDistance })
}
@@ -1861,6 +1865,7 @@ class ElementTest {
SceneA,
transitions {
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
translate(TestElements.Foo, y = 15.dp)
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index b7f50fd8d685..a549d0355a26 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -106,7 +106,7 @@ class MovableElementTest {
rule
.onNode(
hasText("count: 3") and
- hasParent(isElement(TestElements.Foo, scene = SceneA))
+ hasParent(isElement(TestElements.Foo, content = SceneA))
)
.assertExists()
.assertIsNotDisplayed()
@@ -114,7 +114,7 @@ class MovableElementTest {
rule
.onNode(
hasText("count: 0") and
- hasParent(isElement(TestElements.Foo, scene = SceneB))
+ hasParent(isElement(TestElements.Foo, content = SceneB))
)
.assertIsDisplayed()
.assertSizeIsEqualTo(75.dp, 75.dp)
@@ -213,7 +213,7 @@ class MovableElementTest {
rule
.onNode(
hasText("count: 3") and
- hasParent(isElement(TestElements.Foo, scene = SceneA))
+ hasParent(isElement(TestElements.Foo, content = SceneA))
)
.assertIsDisplayed()
.assertSizeIsEqualTo(75.dp, 75.dp)
@@ -234,7 +234,7 @@ class MovableElementTest {
rule
.onNode(
hasText("count: 3") and
- hasParent(isElement(TestElements.Foo, scene = SceneB))
+ hasParent(isElement(TestElements.Foo, content = SceneB))
)
.assertIsDisplayed()
@@ -324,7 +324,7 @@ class MovableElementTest {
fun movableElementScopeExtendsBoxScope() {
val key = MovableElementKey("Foo", contents = setOf(SceneA))
rule.setContent {
- TestContentScope {
+ TestContentScope(currentScene = SceneA) {
MovableElement(key, Modifier.size(200.dp)) {
content {
Box(Modifier.testTag("bottomEnd").align(Alignment.BottomEnd))
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
new file mode 100644
index 000000000000..85db418f6020
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestOverlays.OverlayA
+import com.android.compose.animation.scene.TestOverlays.OverlayB
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.test.assertSizeIsEqualTo
+import kotlinx.coroutines.CoroutineScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class OverlayTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Composable
+ private fun ContentScope.Foo(width: Dp = 100.dp, height: Dp = 100.dp) {
+ Box(Modifier.element(TestElements.Foo).size(width, height))
+ }
+
+ @Test
+ fun showThenHideOverlay() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ lateinit var coroutineScope: CoroutineScope
+ rule.setContent {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+ overlay(OverlayA) { Foo() }
+ }
+ }
+
+ // Initial state: overlay A is not shown, so Foo is displayed at the top left in scene A.
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertIsDisplayed()
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+
+ // Show overlay A: Foo is now centered on screen and placed in overlay A. It is not placed
+ // in scene A.
+ rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+ // Hide overlay A: back to initial state, top-left in scene A.
+ rule.runOnUiThread { state.hideOverlay(OverlayA, coroutineScope) }
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertIsDisplayed()
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ }
+
+ @Test
+ fun multipleOverlays() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ lateinit var coroutineScope: CoroutineScope
+ rule.setContent {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+ overlay(OverlayA) { Foo() }
+ overlay(OverlayB) { Foo() }
+ }
+ }
+
+ // Initial state.
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertIsDisplayed()
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+
+ // Show overlay A.
+ rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+
+ // Replace overlay A by overlay B.
+ rule.runOnUiThread { state.replaceOverlay(OverlayA, OverlayB, coroutineScope) }
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+ // Show overlay A: Foo is still placed in B because it has a higher zIndex, but it now
+ // exists in A as well.
+ rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+ // Hide overlay B.
+ rule.runOnUiThread { state.hideOverlay(OverlayB, coroutineScope) }
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+
+ // Hide overlay A.
+ rule.runOnUiThread { state.hideOverlay(OverlayA, coroutineScope) }
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertIsDisplayed()
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+ }
+
+ @Test
+ fun movableElement() {
+ val key = MovableElementKey("MovableBar", contents = setOf(SceneA, OverlayA, OverlayB))
+ val elementChildTag = "elementChildTag"
+
+ fun elementChild(content: ContentKey) = hasTestTag(elementChildTag) and inContent(content)
+
+ @Composable
+ fun ContentScope.MovableBar() {
+ MovableElement(key, Modifier) {
+ content { Box(Modifier.testTag(elementChildTag).size(100.dp)) }
+ }
+ }
+
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ lateinit var coroutineScope: CoroutineScope
+ rule.setContent {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) { MovableBar() } }
+ overlay(OverlayA) { MovableBar() }
+ overlay(OverlayB) { MovableBar() }
+ }
+ }
+
+ // Initial state.
+ rule
+ .onNode(elementChild(content = SceneA))
+ .assertIsDisplayed()
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(elementChild(content = OverlayA)).assertDoesNotExist()
+ rule.onNode(elementChild(content = OverlayB)).assertDoesNotExist()
+
+ // Show overlay A: movable element child only exists (is only composed) in overlay A.
+ rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+ rule.onNode(elementChild(content = SceneA)).assertDoesNotExist()
+ rule
+ .onNode(elementChild(content = OverlayA))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+ rule.onNode(elementChild(content = OverlayB)).assertDoesNotExist()
+
+ // Replace overlay A by overlay B: element child is only in overlay B.
+ rule.runOnUiThread { state.replaceOverlay(OverlayA, OverlayB, coroutineScope) }
+ rule.onNode(elementChild(content = SceneA)).assertDoesNotExist()
+ rule.onNode(elementChild(content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(elementChild(content = OverlayB))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+ // Show overlay A: element child still only exists in overlay B because it has a higher
+ // zIndex.
+ rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+ rule.onNode(elementChild(content = SceneA)).assertDoesNotExist()
+ rule.onNode(elementChild(content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(elementChild(content = OverlayB))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+ // Hide overlay B: element child is in overlay A.
+ rule.runOnUiThread { state.hideOverlay(OverlayB, coroutineScope) }
+ rule.onNode(elementChild(content = SceneA)).assertDoesNotExist()
+ rule
+ .onNode(elementChild(content = OverlayA))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+ rule.onNode(elementChild(content = OverlayB)).assertDoesNotExist()
+
+ // Hide overlay A: element child is in scene A.
+ rule.runOnUiThread { state.hideOverlay(OverlayA, coroutineScope) }
+ rule
+ .onNode(elementChild(content = SceneA))
+ .assertIsDisplayed()
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(elementChild(content = OverlayA)).assertDoesNotExist()
+ rule.onNode(elementChild(content = OverlayB)).assertDoesNotExist()
+ }
+
+ @Test
+ fun overlayAlignment() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ }
+ var alignment by mutableStateOf(Alignment.Center)
+ rule.setContent {
+ SceneTransitionLayout(state, Modifier.size(200.dp)) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+ overlay(OverlayA, alignment) { Foo() }
+ }
+ }
+
+ // Initial state: 100x100dp centered in 200x200dp layout.
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+ // BottomStart.
+ alignment = Alignment.BottomStart
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 100.dp)
+
+ // TopEnd.
+ alignment = Alignment.TopEnd
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp)
+ .assertPositionInRootIsEqualTo(100.dp, 0.dp)
+ }
+
+ @Test
+ fun overlayMaxSizeIsCurrentSceneSize() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ }
+
+ val contentTag = "overlayContent"
+ rule.setContent {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Box(Modifier.size(100.dp)) { Foo() } }
+ overlay(OverlayA) { Box(Modifier.testTag(contentTag).fillMaxSize()) }
+ }
+ }
+
+ // Max overlay size is the size of the layout without overlays, not the (max) possible size
+ // of the layout.
+ rule.onNodeWithTag(contentTag).assertSizeIsEqualTo(100.dp)
+ }
+
+ @Test
+ fun showAnimation() {
+ rule.testShowOverlayTransition(
+ fromSceneContent = {
+ Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+ Foo(width = 60.dp, height = 40.dp)
+ }
+ },
+ overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+ // size of 100x80dp, so at (40,20).
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ after {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+ }
+ }
+
+ @Test
+ fun hideAnimation() {
+ rule.testHideOverlayTransition(
+ toSceneContent = {
+ Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+ Foo(width = 60.dp, height = 40.dp)
+ }
+ },
+ overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from centered (in a 180x120dp Box) with a size of 100x80dp, so at (40,20),
+ // to (0,0) with a size of 60x40dp.
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ after {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ }
+ }
+ }
+
+ @Test
+ fun replaceAnimation() {
+ rule.testReplaceOverlayTransition(
+ currentSceneContent = { Box(Modifier.size(width = 180.dp, height = 120.dp)) },
+ fromContent = { Foo(width = 60.dp, height = 40.dp) },
+ fromAlignment = Alignment.TopStart,
+ toContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+ // size of 100x80dp, so at (40,20).
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ after {
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 3422a8e47a3d..69f2cbace276 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -495,7 +495,7 @@ class SceneTransitionLayoutStateTest {
// overscroll for SceneB is defined
progress.value = 1.1f
val overscrollSpec = assertThat(transition).hasOverscrollSpec()
- assertThat(overscrollSpec.scene).isEqualTo(SceneB)
+ assertThat(overscrollSpec.content).isEqualTo(SceneB)
}
@Test
@@ -516,7 +516,7 @@ class SceneTransitionLayoutStateTest {
// overscroll for SceneA is defined
progress.value = -0.1f
val overscrollSpec = assertThat(transition).hasOverscrollSpec()
- assertThat(overscrollSpec.scene).isEqualTo(SceneA)
+ assertThat(overscrollSpec.content).isEqualTo(SceneA)
// scroll from SceneA to SceneB
progress.value = 0.5f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index e97c27e5a034..b8e13dab913b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -500,4 +500,19 @@ class SceneTransitionLayoutTest {
assertThat(keyInB).isEqualTo(SceneB)
assertThat(keyInC).isEqualTo(SceneC)
}
+
+ @Test
+ fun overlaysMapIsNotAllocatedWhenNoOverlayIsDefined() {
+ lateinit var layoutImpl: SceneTransitionLayoutImpl
+ rule.setContent {
+ SceneTransitionLayoutForTesting(
+ remember { MutableSceneTransitionLayoutState(SceneA) },
+ onLayoutImpl = { layoutImpl = it },
+ ) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ assertThat(layoutImpl.overlaysOrNullForTest()).isNull()
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 06799bcda0ef..e48cd81765cb 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -614,6 +614,7 @@ class SwipeToSceneTest {
from(SceneA, to = SceneB) { distance = FixedDistance(swipeDistance) }
overscroll(SceneB, Orientation.Vertical) {
+ progressConverter = ProgressConverter.linear()
translate(TestElements.Foo, x = { 20.dp.toPx() }, y = { 30.dp.toPx() })
}
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 00adefb150c1..5cccfb1b319f 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -26,9 +26,9 @@ import androidx.compose.ui.Modifier
@Composable
fun TestContentScope(
modifier: Modifier = Modifier,
+ currentScene: SceneKey = remember { SceneKey("current") },
content: @Composable ContentScope.() -> Unit,
) {
- val currentScene = remember { SceneKey("current") }
val state = remember { MutableSceneTransitionLayoutState(currentScene) }
SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
index 6d063a0418d6..22450d32ea62 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
@@ -20,11 +20,16 @@ import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.hasTestTag
-/** A [SemanticsMatcher] that matches [element], optionally restricted to scene [scene]. */
-fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher {
- return if (scene == null) {
+/** A [SemanticsMatcher] that matches [element], optionally restricted to content [content]. */
+fun isElement(element: ElementKey, content: ContentKey? = null): SemanticsMatcher {
+ return if (content == null) {
hasTestTag(element.testTag)
} else {
- hasTestTag(element.testTag) and hasAnyAncestor(hasTestTag(scene.testTag))
+ hasTestTag(element.testTag) and inContent(content)
}
}
+
+/** A [SemanticsMatcher] that matches anything inside [content]. */
+fun inContent(content: ContentKey): SemanticsMatcher {
+ return hasAnyAncestor(hasTestTag(content.testTag))
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 7f26b9855a9a..1ebd3d98471b 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -16,9 +16,12 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -115,6 +118,97 @@ fun ComposeContentTestRule.testTransition(
)
}
+/** Test the transition when showing [overlay] from [fromScene]. */
+fun ComposeContentTestRule.testShowOverlayTransition(
+ fromSceneContent: @Composable ContentScope.() -> Unit,
+ overlayContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ fromScene: SceneKey = TestScenes.SceneA,
+ overlay: OverlayKey = TestOverlays.OverlayA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ fromScene,
+ transitions = transitions { from(fromScene, overlay, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(fromScene) { fromSceneContent() }
+ overlay(overlay) { overlayContent() }
+ }
+ },
+ changeState = { state -> state.showOverlay(overlay, animationScope = this) },
+ builder = builder,
+ )
+}
+
+/** Test the transition when hiding [overlay] to [toScene]. */
+fun ComposeContentTestRule.testHideOverlayTransition(
+ toSceneContent: @Composable ContentScope.() -> Unit,
+ overlayContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ toScene: SceneKey = TestScenes.SceneA,
+ overlay: OverlayKey = TestOverlays.OverlayA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ toScene,
+ initialOverlays = setOf(overlay),
+ transitions = transitions { from(overlay, toScene, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(toScene) { toSceneContent() }
+ overlay(overlay) { overlayContent() }
+ }
+ },
+ changeState = { state -> state.hideOverlay(overlay, animationScope = this) },
+ builder = builder,
+ )
+}
+
+/** Test the transition when replace [from] to [to]. */
+fun ComposeContentTestRule.testReplaceOverlayTransition(
+ fromContent: @Composable ContentScope.() -> Unit,
+ toContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ currentSceneContent: @Composable ContentScope.() -> Unit = { Box(Modifier.fillMaxSize()) },
+ fromAlignment: Alignment = Alignment.Center,
+ toAlignment: Alignment = Alignment.Center,
+ from: OverlayKey = TestOverlays.OverlayA,
+ to: OverlayKey = TestOverlays.OverlayB,
+ currentScene: SceneKey = TestScenes.SceneA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ currentScene,
+ initialOverlays = setOf(from),
+ transitions = transitions { from(from, to, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(currentScene) { currentSceneContent() }
+ overlay(from, fromAlignment) { fromContent() }
+ overlay(to, toAlignment) { toContent() }
+ }
+ },
+ changeState = { state -> state.replaceOverlay(from, to, animationScope = this) },
+ builder = builder,
+ )
+}
+
data class TransitionRecordingSpec(
val recordBefore: Boolean = true,
val recordAfter: Boolean = true,
@@ -188,6 +282,21 @@ fun ComposeContentTestRule.testTransition(
"(${currentScene.debugName})"
}
+ testTransition(
+ state = state,
+ changeState = { state -> state.setTargetScene(to, coroutineScope = this) },
+ transitionLayout = transitionLayout,
+ builder = builder,
+ )
+}
+
+/** Test the transition from [state] to [to]. */
+fun ComposeContentTestRule.testTransition(
+ state: MutableSceneTransitionLayoutState,
+ changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
+ transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
val test = transitionTest(builder)
val assertionScope =
object : TransitionTestAssertionScope {
@@ -213,7 +322,7 @@ fun ComposeContentTestRule.testTransition(
mainClock.autoAdvance = false
// Change the current scene.
- runOnUiThread { state.setTargetScene(to, coroutineScope) }
+ runOnUiThread { coroutineScope.changeState(state) }
waitForIdle()
mainClock.advanceTimeByFrame()
waitForIdle()
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
index b83705aa64fc..f39dd676fb6e 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
@@ -21,7 +21,7 @@ import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
-/** Scenes keys that can be reused by tests. */
+/** Scene keys that can be reused by tests. */
object TestScenes {
val SceneA = SceneKey("SceneA")
val SceneB = SceneKey("SceneB")
@@ -29,6 +29,12 @@ object TestScenes {
val SceneD = SceneKey("SceneD")
}
+/** Overlay keys that can be reused by tests. */
+object TestOverlays {
+ val OverlayA = OverlayKey("OverlayA")
+ val OverlayB = OverlayKey("OverlayB")
+}
+
/** Element keys that can be reused by tests. */
object TestElements {
val Foo = ElementKey("Foo")
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS
new file mode 100644
index 000000000000..f6f98e934dde
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
index 1ce21e77f7f3..1ce21e77f7f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
index cb8cfc2f5dd6..cb8cfc2f5dd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index c74d340ee325..c74d340ee325 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
index 7b06dd65e7b3..7b06dd65e7b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 1ceac78af1a2..1ceac78af1a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
index 3cd3fefb8ef0..3cd3fefb8ef0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
index 8f9b7c8cbc45..8f9b7c8cbc45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index e1e515eb31f5..e1e515eb31f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java
index 550e77d63c3b..550e77d63c3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/SystemActionsTest.java
index 53b98d52e9d1..53b98d52e9d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/SystemActionsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java
index 859517839388..859517839388 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 25696bffdd66..25696bffdd66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
index c4a92bf18283..c4a92bf18283 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
index b80836d80e12..b80836d80e12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
index 1386092ef93e..1386092ef93e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
index ebe7500300c8..ebe7500300c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
index 5b2afe7443dd..5b2afe7443dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index d7acaaf796f8..d7acaaf796f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
index b59773700ed5..b59773700ed5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
index 24f3a29e64ee..24f3a29e64ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 4373c880d999..4373c880d999 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index fcdeff9ab683..fcdeff9ab683 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
index 8fb71faf71a9..8fb71faf71a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
index 7320bbff843e..7320bbff843e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
index 09aa286874b9..09aa286874b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
index 9359adf96f80..9359adf96f80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
index 2ac5d105ba99..2ac5d105ba99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
index 17ce1ddee87a..17ce1ddee87a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
index 8399fa85bfb1..8399fa85bfb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
index 201ed00acae3..43db5a70849f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
@@ -148,6 +148,7 @@ public class AmbientStatusBarViewControllerTest extends SysuiTestCase {
mKosmos.getWifiInteractor(),
mKosmos.getCommunalSceneInteractor(),
mLogBuffer);
+ mController.onInit();
}
@Test
@@ -517,6 +518,15 @@ public class AmbientStatusBarViewControllerTest extends SysuiTestCase {
verify(mDreamOverlayStateController).setDreamOverlayStatusBarVisible(false);
}
+ @Test
+ public void testStatusBarWindowStateControllerListenerLifecycle() {
+ ArgumentCaptor<StatusBarWindowStateListener> listenerCaptor =
+ ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+ verify(mStatusBarWindowStateController).addListener(listenerCaptor.capture());
+ mController.destroy();
+ verify(mStatusBarWindowStateController).removeListener(eq(listenerCaptor.getValue()));
+ }
+
private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
final ArgumentCaptor<StatusBarWindowStateListener>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/InputSessionTest.java
index 8fca557c7832..8fca557c7832 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/InputSessionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
index b0f81c012cca..b0f81c012cca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
index e492c63d095c..e492c63d095c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
index 4809d0e4838f..4809d0e4838f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackTransformationTest.kt
index d898d1cc0f8e..d898d1cc0f8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackTransformationTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
index 9548e297e7c5..9548e297e7c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
index 828d36741aeb..828d36741aeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index cbad133ba4f0..cbad133ba4f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
index 99d36003dfef..99d36003dfef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 7a4bbfe9a580..7a4bbfe9a580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatterySpecsTest.kt
index cac0b664ab79..cac0b664ab79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatterySpecsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
index 197cb843ba5f..197cb843ba5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
index 9b0e58d63952..9b0e58d63952 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
index baef620ad556..baef620ad556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index 13306becf6d2..13306becf6d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
index 921ff098753e..921ff098753e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
index d26ccbcd12e0..d26ccbcd12e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
index d2150471744e..d2150471744e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index d9b71619992f..d9b71619992f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
index 9c114054bcfb..9c114054bcfb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
index 0209ab803368..0209ab803368 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index ff5a419faf35..ff5a419faf35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 22971bcf799e..22971bcf799e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
index 5d2d20ce88e9..5d2d20ce88e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
index f40b6b046187..f40b6b046187 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 13f2c7212e36..13f2c7212e36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
index f9bedc93e193..f9bedc93e193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
index 33ddbf1989b3..33ddbf1989b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
index 3863b3ccdaee..3863b3ccdaee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index a4653e736745..a4653e736745 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 7fa165c19f60..7fa165c19f60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index 0d01472b45c7..0d01472b45c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
index 77ddd3183b00..77ddd3183b00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
index 3eb2ff301212..3eb2ff301212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
index 81132d72f86c..81132d72f86c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 0db7b62b8ef1..0db7b62b8ef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
index ac5ceb8ed266..ac5ceb8ed266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
index 969e26a8d884..969e26a8d884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
index f06b105a9e26..f06b105a9e26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
index 5ff46346b386..5ff46346b386 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index 15a4a40c99ac..65c9b72a3665 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -39,7 +39,6 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
@@ -114,8 +113,6 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
systemPropertiesHelper = systemPropertiesHelper,
primaryBouncerInteractor = kosmos.primaryBouncerInteractor,
facePropertyRepository = kosmos.fakeFacePropertyRepository,
- deviceEntryFingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
- faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository,
securityModel = securityModel,
deviceEntryBiometricsAllowedInteractor =
kosmos.deviceEntryBiometricsAllowedInteractor,
@@ -217,7 +214,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
fun resetMessageBackToDefault_faceAuthRestarts() =
testScope.runTest {
init()
- verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+ captureKeyguardUpdateMonitorCallback()
val bouncerMessage by collectLastValue(underTest.bouncerMessage)
underTest.setFaceAcquisitionMessage("not empty")
@@ -240,7 +237,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
fun faceRestartDoesNotResetFingerprintMessage() =
testScope.runTest {
init()
- verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+ captureKeyguardUpdateMonitorCallback()
val bouncerMessage by collectLastValue(underTest.bouncerMessage)
underTest.setFingerprintAcquisitionMessage("not empty")
@@ -337,6 +334,32 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
}
@Test
+ fun faceLockoutThenFaceFailure_doesNotUpdateMessage() =
+ testScope.runTest {
+ init()
+ captureKeyguardUpdateMonitorCallback()
+ val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true)
+ runCurrent()
+
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(secondaryResMessage(bouncerMessage))
+ .isEqualTo("Can’t unlock with face. Too many attempts.")
+
+ // WHEN face failure comes in during lockout
+ keyguardUpdateMonitorCaptor.value.onBiometricAuthFailed(BiometricSourceType.FACE)
+
+ // THEN lockout message does NOT update to face failure message
+ assertThat(primaryResMessage(bouncerMessage))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(secondaryResMessage(bouncerMessage))
+ .isEqualTo("Can’t unlock with face. Too many attempts.")
+ }
+
+ @Test
fun onFaceLockoutStateChange_whenFaceIsNotEnrolled_isANoop() =
testScope.runTest {
init()
@@ -629,6 +652,10 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
}
}
+ private fun captureKeyguardUpdateMonitorCallback() {
+ verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+ }
+
companion object {
private const val PRIMARY_USER_ID = 0
private val PRIMARY_USER =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
index 923687b9375d..923687b9375d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
index 1e9f8558d73c..1e9f8558d73c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
index c693ecc7252f..c693ecc7252f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraIntentsTest.kt
index 34940246a7cb..34940246a7cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraIntentsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index ec8cc4d493d0..ec8cc4d493d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ClassifierTest.java
index 4da151c3ca04..4da151c3ca04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
index 8e1be4160498..8e1be4160498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
index 9289867cbfe2..9289867cbfe2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
index 8e4bec3f2e50..8e4bec3f2e50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 5d0bfd7d3d87..5d0bfd7d3d87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/HistoryTrackerTest.java
index 8e19a1f84d72..8e19a1f84d72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/HistoryTrackerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ProximityClassifierTest.java
index f965a11c2aa9..f965a11c2aa9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ProximityClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
index 65e90888ecb2..65e90888ecb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
index 9a27f386d519..9a27f386d519 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TypeClassifierTest.java
index 80c44e2537ec..80c44e2537ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TypeClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
index 791f1f2e1f26..791f1f2e1f26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
index 63e43d777a78..63e43d777a78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
index ea6cb3b6d178..ea6cb3b6d178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 882bcab5fab6..882bcab5fab6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
index de07cda21e75..de07cda21e75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
index 5556b04c2d20..4c908dd895c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
@@ -140,6 +140,41 @@ class PackageInstallerMonitorTest : SysuiTestCase() {
}
@Test
+ fun installSessions_ignoreNullPackageNameSessions() =
+ testScope.runTest {
+ val nullPackageSession =
+ SessionInfo().apply {
+ sessionId = 1
+ appPackageName = null
+ appIcon = icon1
+ }
+ val wellFormedSession =
+ SessionInfo().apply {
+ sessionId = 2
+ appPackageName = "pkg_name"
+ appIcon = icon2
+ }
+
+ defaultSessions = listOf(nullPackageSession, wellFormedSession)
+
+ whenever(packageInstaller.allSessions).thenReturn(defaultSessions)
+ whenever(packageInstaller.getSessionInfo(1)).thenReturn(nullPackageSession)
+ whenever(packageInstaller.getSessionInfo(2)).thenReturn(wellFormedSession)
+
+ val packageInstallerMonitor =
+ PackageInstallerMonitor(
+ handler,
+ kosmos.applicationCoroutineScope,
+ logcatLogBuffer("PackageInstallerRepositoryImplTest"),
+ packageInstaller,
+ )
+
+ val sessions by
+ testScope.collectLastValue(packageInstallerMonitor.installSessionsForPrimaryUser)
+ assertThat(sessions?.size).isEqualTo(1)
+ }
+
+ @Test
fun installSessions_newSessionsAreAdded() =
testScope.runTest {
val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
@@ -177,7 +212,7 @@ class PackageInstallerMonitorTest : SysuiTestCase() {
}
// Session 1 finished successfully
- callback.onFinished(1, /* success = */ true)
+ callback.onFinished(1, /* success= */ true)
runCurrent()
// Verify flow updated with session 1 removed
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
index a308c8ee38ca..a308c8ee38ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index 72e0726dedb0..72e0726dedb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
index bb400f274fbe..bb400f274fbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index cecb5251b6e2..cecb5251b6e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index 983a43561590..edc8c837bf78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -69,19 +69,19 @@ class CommunalBackupUtilsTest : SysuiTestCase() {
FakeWidgetMetadata(
widgetId = 11,
componentName = "com.android.fakePackage1/fakeWidget1",
- rank = 3,
+ rank = 0,
userSerialNumber = 0,
),
FakeWidgetMetadata(
widgetId = 12,
componentName = "com.android.fakePackage2/fakeWidget2",
- rank = 2,
+ rank = 1,
userSerialNumber = 0,
),
FakeWidgetMetadata(
widgetId = 13,
componentName = "com.android.fakePackage3/fakeWidget3",
- rank = 1,
+ rank = 2,
userSerialNumber = 10,
),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index d6705085eafd..d4d966ad2ef7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -67,11 +67,11 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
@Test
fun addWidget_readValueInDb() =
testScope.runTest {
- val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
+ val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
@@ -81,11 +81,11 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
@Test
fun deleteWidget_notInDb_returnsFalse() =
testScope.runTest {
- val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
+ val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
@@ -97,11 +97,11 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber
)
}
@@ -115,17 +115,48 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
}
@Test
+ fun addWidget_rankNotSpecified_widgetAddedAtTheEnd(): Unit =
+ testScope.runTest {
+ val widgets by collectLastValue(communalWidgetDao.getWidgets())
+
+ // Verify database is empty
+ assertThat(widgets).isEmpty()
+
+ // Add widgets one by one without specifying rank
+ val widgetsToAdd = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
+ widgetsToAdd.forEach {
+ val (widgetId, provider, _, userSerialNumber) = it
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ userSerialNumber = userSerialNumber
+ )
+ }
+
+ // Verify new each widget is added at the end
+ assertThat(widgets)
+ .containsExactly(
+ communalItemRankEntry1,
+ communalWidgetItemEntry1,
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
+ communalItemRankEntry3,
+ communalWidgetItemEntry3,
+ )
+ }
+
+ @Test
fun deleteWidget_emitsActiveWidgetsInDb() =
testScope.runTest {
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
}
@@ -148,32 +179,32 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
.containsExactly(
- communalItemRankEntry2,
- communalWidgetItemEntry2,
communalItemRankEntry1,
communalWidgetItemEntry1,
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
)
.inOrder()
- // swapped priorities
- val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
- communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+ // swapped ranks
+ val widgetIdsToRankMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 0)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToRankMap)
assertThat(widgets())
.containsExactly(
- communalItemRankEntry1.copy(rank = 2),
+ communalItemRankEntry2.copy(rank = 0),
+ communalWidgetItemEntry2,
+ communalItemRankEntry1.copy(rank = 1),
communalWidgetItemEntry1,
- communalItemRankEntry2.copy(rank = 1),
- communalWidgetItemEntry2
)
.inOrder()
}
@@ -181,53 +212,56 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
@Test
fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
testScope.runTest {
- val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+ val existingWidgets = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
existingWidgets.forEach {
- val (widgetId, provider, priority, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
.containsExactly(
- communalItemRankEntry2,
- communalWidgetItemEntry2,
communalItemRankEntry1,
communalWidgetItemEntry1,
- )
- .inOrder()
-
- // map with no item in the middle at index 1
- val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
- communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
- assertThat(widgets())
- .containsExactly(
- communalItemRankEntry2.copy(rank = 3),
+ communalItemRankEntry2,
communalWidgetItemEntry2,
- communalItemRankEntry1.copy(rank = 1),
- communalWidgetItemEntry1,
+ communalItemRankEntry3,
+ communalWidgetItemEntry3,
)
.inOrder()
- // add the new middle item that we left space for.
+
+ // add a new widget at rank 1.
communalWidgetDao.addWidget(
- widgetId = widgetInfo3.widgetId,
- provider = widgetInfo3.provider,
- priority = 2,
- userSerialNumber = widgetInfo3.userSerialNumber,
+ widgetId = 4,
+ provider = ComponentName("pk_name", "cls_name_4"),
+ rank = 1,
+ userSerialNumber = 0,
)
+
+ val newRankEntry = CommunalItemRank(uid = 4L, rank = 1)
+ val newWidgetEntry =
+ CommunalWidgetItem(
+ uid = 4L,
+ widgetId = 4,
+ componentName = "pk_name/cls_name_4",
+ itemId = 4L,
+ userSerialNumber = 0,
+ )
assertThat(widgets())
.containsExactly(
- communalItemRankEntry2.copy(rank = 3),
+ communalItemRankEntry1.copy(rank = 0),
+ communalWidgetItemEntry1,
+ newRankEntry,
+ newWidgetEntry,
+ communalItemRankEntry2.copy(rank = 2),
communalWidgetItemEntry2,
- communalItemRankEntry3.copy(rank = 2),
+ communalItemRankEntry3.copy(rank = 3),
communalWidgetItemEntry3,
- communalItemRankEntry1.copy(rank = 1),
- communalWidgetItemEntry1,
)
.inOrder()
}
@@ -261,11 +295,11 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
assertThat(widgets).containsExactlyEntriesIn(expected)
}
- private fun addWidget(metadata: FakeWidgetMetadata, priority: Int? = null) {
+ private fun addWidget(metadata: FakeWidgetMetadata, rank: Int? = null) {
communalWidgetDao.addWidget(
widgetId = metadata.widgetId,
provider = metadata.provider,
- priority = priority ?: metadata.priority,
+ rank = rank ?: metadata.rank,
userSerialNumber = metadata.userSerialNumber,
)
}
@@ -273,7 +307,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
data class FakeWidgetMetadata(
val widgetId: Int,
val provider: ComponentName,
- val priority: Int,
+ val rank: Int,
val userSerialNumber: Int,
)
@@ -282,26 +316,26 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
FakeWidgetMetadata(
widgetId = 1,
provider = ComponentName("pk_name", "cls_name_1"),
- priority = 1,
+ rank = 0,
userSerialNumber = 0,
)
val widgetInfo2 =
FakeWidgetMetadata(
widgetId = 2,
provider = ComponentName("pk_name", "cls_name_2"),
- priority = 2,
+ rank = 1,
userSerialNumber = 0,
)
val widgetInfo3 =
FakeWidgetMetadata(
widgetId = 3,
provider = ComponentName("pk_name", "cls_name_3"),
- priority = 3,
+ rank = 2,
userSerialNumber = 10,
)
- val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
- val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
- val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
+ val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.rank)
+ val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.rank)
+ val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.rank)
val communalWidgetItemEntry1 =
CommunalWidgetItem(
uid = 1L,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
index ad2c42fd1f09..eba395bdb5a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -115,21 +115,21 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
.addWidget(
widgetId = 0,
componentName = defaultWidgets[0],
- priority = 3,
+ rank = 0,
userSerialNumber = 0,
)
verify(communalWidgetDao)
.addWidget(
widgetId = 1,
componentName = defaultWidgets[1],
- priority = 2,
+ rank = 1,
userSerialNumber = 0,
)
verify(communalWidgetDao)
.addWidget(
widgetId = 2,
componentName = defaultWidgets[2],
- priority = 1,
+ rank = 2,
userSerialNumber = 0,
)
}
@@ -150,7 +150,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
.addWidget(
widgetId = anyInt(),
componentName = any(),
- priority = anyInt(),
+ rank = anyInt(),
userSerialNumber = anyInt(),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index ca81838bcdaa..980a5ec8c494 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -154,7 +154,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
CommunalWidgetContentModel.Available(
appWidgetId = communalWidgetItemEntry.widgetId,
providerInfo = providerInfoA,
- priority = communalItemRankEntry.rank,
+ rank = communalItemRankEntry.rank,
)
)
@@ -190,12 +190,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
CommunalWidgetContentModel.Available(
appWidgetId = 1,
providerInfo = providerInfoA,
- priority = 1,
+ rank = 1,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
- priority = 2,
+ rank = 2,
),
)
}
@@ -225,12 +225,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
CommunalWidgetContentModel.Available(
appWidgetId = 1,
providerInfo = providerInfoA,
- priority = 1,
+ rank = 1,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
- priority = 2,
+ rank = 2,
),
)
@@ -248,12 +248,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
appWidgetId = 1,
// Verify that provider info updated
providerInfo = providerInfoC,
- priority = 1,
+ rank = 1,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
- priority = 2,
+ rank = 2,
),
)
}
@@ -263,7 +263,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- val priority = 1
+ val rank = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -273,12 +273,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
)
)
.thenReturn(id)
- underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorSuccess)
+ underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorSuccess)
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
- verify(communalWidgetDao)
- .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
+ verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -289,7 +288,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- val priority = 1
+ val rank = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -299,7 +298,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
)
)
.thenReturn(id)
- underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
+ underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorFail)
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
@@ -316,7 +315,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- val priority = 1
+ val rank = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -326,7 +325,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
)
)
.thenReturn(id)
- underTest.addWidget(provider, mainUser, priority) {
+ underTest.addWidget(provider, mainUser, rank) {
throw IllegalStateException("some error")
}
runCurrent()
@@ -345,7 +344,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- val priority = 1
+ val rank = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
whenever(
@@ -355,12 +354,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
)
)
.thenReturn(id)
- underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
+ underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorFail)
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
- verify(communalWidgetDao)
- .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
+ verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -399,11 +397,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun reorderWidgets_queryDb() =
testScope.runTest {
- val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
- underTest.updateWidgetOrder(widgetIdToPriorityMap)
+ val widgetIdToRankMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+ underTest.updateWidgetOrder(widgetIdToRankMap)
runCurrent()
- verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
+ verify(communalWidgetDao).updateWidgetOrder(widgetIdToRankMap)
// Verify backup requested
verify(backupManager).dataChanged()
@@ -691,11 +689,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
CommunalWidgetContentModel.Available(
appWidgetId = 1,
providerInfo = providerInfoA,
- priority = 1,
+ rank = 1,
),
CommunalWidgetContentModel.Pending(
appWidgetId = 2,
- priority = 2,
+ rank = 2,
componentName = ComponentName("pk_2", "cls_2"),
icon = fakeIcon,
user = mainUser,
@@ -730,7 +728,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
.containsExactly(
CommunalWidgetContentModel.Pending(
appWidgetId = 1,
- priority = 1,
+ rank = 1,
componentName = ComponentName("pk_1", "cls_1"),
icon = fakeIcon,
user = mainUser,
@@ -750,7 +748,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
CommunalWidgetContentModel.Available(
appWidgetId = 1,
providerInfo = providerInfoA,
- priority = 1,
+ rank = 1,
),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 864795b062be..1d03ced19c72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -791,14 +791,6 @@ class CommunalInteractorTest : SysuiTestCase() {
}
@Test
- fun showWidgetEditor_withPreselectedKey_startsActivity() =
- testScope.runTest {
- val widgetKey = CommunalContentModel.KEY.widget(123)
- underTest.showWidgetEditor(preselectedKey = widgetKey)
- verify(editWidgetsActivityStarter).startActivity(widgetKey)
- }
-
- @Test
fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() =
testScope.runTest {
underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true)
@@ -1082,6 +1074,16 @@ class CommunalInteractorTest : SysuiTestCase() {
assertThat(disclaimerDismissed).isFalse()
}
+ @Test
+ fun settingSelectedKey_flowUpdated() {
+ testScope.runTest {
+ val key = "test"
+ val selectedKey by collectLastValue(underTest.selectedKey)
+ underTest.setSelectedKey(key)
+ assertThat(selectedKey).isEqualTo(key)
+ }
+ }
+
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index ed7e9107240e..dfb75cae6ecd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -22,6 +22,7 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor.OnSceneAboutToChangeListener
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.EditModeState
@@ -36,6 +37,11 @@ import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -58,6 +64,36 @@ class CommunalSceneInteractorTest : SysuiTestCase() {
}
@Test
+ fun changeScene_callsSceneStateProcessor() =
+ testScope.runTest {
+ val callback: OnSceneAboutToChangeListener = mock()
+ underTest.registerSceneStateProcessor(callback)
+
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+ verify(callback, never()).onSceneAboutToChange(any(), anyOrNull())
+
+ underTest.changeScene(CommunalScenes.Communal, "test")
+ assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+ verify(callback).onSceneAboutToChange(CommunalScenes.Communal, null)
+ }
+
+ @Test
+ fun changeScene_doesNotCallSceneStateProcessorForDuplicateState() =
+ testScope.runTest {
+ val callback: OnSceneAboutToChangeListener = mock()
+ underTest.registerSceneStateProcessor(callback)
+
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ underTest.changeScene(CommunalScenes.Blank, "test")
+ assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ verify(callback, never()).onSceneAboutToChange(any(), anyOrNull())
+ }
+
+ @Test
fun snapToScene() =
testScope.runTest {
val currentScene by collectLastValue(underTest.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 7e28e19d0ee0..0bfcd242828d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -36,11 +36,15 @@ import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTutorialInteractorTest : SysuiTestCase() {
@@ -50,14 +54,14 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
private lateinit var underTest: CommunalTutorialInteractor
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
- private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var communalSceneInteractor: CommunalSceneInteractor
private lateinit var userRepository: FakeUserRepository
@Before
fun setUp() {
keyguardRepository = kosmos.fakeKeyguardRepository
communalTutorialRepository = kosmos.fakeCommunalTutorialRepository
- communalInteractor = kosmos.communalInteractor
+ communalSceneInteractor = kosmos.communalSceneInteractor
userRepository = kosmos.fakeUserRepository
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
@@ -158,7 +162,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
kosmos.setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
- communalInteractor.changeScene(CommunalScenes.Blank, "test")
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
}
@@ -171,7 +175,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
- communalInteractor.changeScene(CommunalScenes.Blank, "test")
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
@@ -184,13 +188,14 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalInteractor.changeScene(CommunalScenes.Blank, "test")
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
- private suspend fun goToCommunal() {
+ private suspend fun TestScope.goToCommunal() {
kosmos.setCommunalAvailable(true)
- communalInteractor.changeScene(CommunalScenes.Communal, "test")
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 57ce9de2d057..82181788e1be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -150,8 +150,8 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Smartspace available.
smartspaceRepository.setTimers(
@@ -212,8 +212,8 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
val communalContent by collectLastValue(underTest.communalContent)
@@ -227,7 +227,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
underTest.onDeleteWidget(
id = 0,
componentName = ComponentName("test_package", "test_class"),
- priority = 30,
+ rank = 30,
)
// Only one widget and CTA tile remain.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index cc945d63e15f..f6f5bc038209 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -101,7 +101,6 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
-import org.mockito.kotlin.times
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -160,24 +159,27 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
communalInteractor = spy(kosmos.communalInteractor)
- underTest =
- CommunalViewModel(
- kosmos.testDispatcher,
- testScope,
- kosmos.testScope.backgroundScope,
- context.resources,
- kosmos.keyguardTransitionInteractor,
- kosmos.keyguardInteractor,
- mock<KeyguardIndicationController>(),
- kosmos.communalSceneInteractor,
- communalInteractor,
- kosmos.communalSettingsInteractor,
- kosmos.communalTutorialInteractor,
- kosmos.shadeInteractor,
- mediaHost,
- logcatLogBuffer("CommunalViewModelTest"),
- metricsLogger,
- )
+ underTest = createViewModel()
+ }
+
+ private fun createViewModel(): CommunalViewModel {
+ return CommunalViewModel(
+ kosmos.testDispatcher,
+ testScope,
+ kosmos.testScope.backgroundScope,
+ context.resources,
+ kosmos.keyguardTransitionInteractor,
+ kosmos.keyguardInteractor,
+ mock<KeyguardIndicationController>(),
+ kosmos.communalSceneInteractor,
+ communalInteractor,
+ kosmos.communalSettingsInteractor,
+ kosmos.communalTutorialInteractor,
+ kosmos.shadeInteractor,
+ mediaHost,
+ logcatLogBuffer("CommunalViewModelTest"),
+ metricsLogger,
+ )
}
@Test
@@ -213,8 +215,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Smartspace available.
smartspaceRepository.setTimers(
@@ -303,7 +305,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- widgetRepository.addWidget(appWidgetId = 1, priority = 1)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 1)
mediaRepository.mediaInactive()
smartspaceRepository.setTimers(emptyList())
@@ -660,8 +662,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
)
// Widgets available
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Then hub shows widgets and the CTA tile
assertThat(communalContent).hasSize(3)
@@ -716,8 +718,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
)
// And widgets available
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Then emits widgets and the CTA tile
assertThat(communalContent).hasSize(3)
@@ -770,7 +772,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun onTapWidget_logEvent() {
- underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), priority = 10)
+ underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), rank = 10)
verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
}
@@ -785,6 +787,21 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(touchAvailable).isTrue()
}
+ @Test
+ fun selectedKey_changeAffectsAllInstances() =
+ testScope.runTest {
+ val model1 = createViewModel()
+ val selectedKey1 by collectLastValue(model1.selectedKey)
+ val model2 = createViewModel()
+ val selectedKey2 by collectLastValue(model2.selectedKey)
+
+ val key = "test"
+ model1.setSelectedKey(key)
+
+ assertThat(selectedKey1).isEqualTo(key)
+ assertThat(selectedKey2).isEqualTo(key)
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
new file mode 100644
index 000000000000..3ba86254d2f4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EditWidgetsActivityControllerTest : SysuiTestCase() {
+ @Test
+ fun activityLifecycle_finishedWhenNotWaitingForResult() {
+ val activity = mock<Activity>()
+ val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.setActivityFullyVisible(true)
+ callbackCapture.lastValue.onActivityStopped(activity)
+
+ verify(activity).finish()
+ }
+
+ @Test
+ fun activityLifecycle_notFinishedWhenOnStartCalledAfterOnStop() {
+ val activity = mock<Activity>()
+
+ val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.setActivityFullyVisible(false)
+ callbackCapture.lastValue.onActivityStopped(activity)
+ callbackCapture.lastValue.onActivityStarted(activity)
+
+ verify(activity, never()).finish()
+ }
+
+ @Test
+ fun activityLifecycle_notFinishedDuringConfigurationChange() {
+ val activity = mock<Activity>()
+
+ val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.setActivityFullyVisible(true)
+ whenever(activity.isChangingConfigurations).thenReturn(true)
+ callbackCapture.lastValue.onActivityStopped(activity)
+ callbackCapture.lastValue.onActivityStarted(activity)
+
+ verify(activity, never()).finish()
+ }
+
+ @Test
+ fun activityLifecycle_notFinishedWhenWaitingForResult() {
+ val activity = mock<Activity>()
+ val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.onWaitingForResult(true)
+ callbackCapture.lastValue.onActivityStopped(activity)
+
+ verify(activity, never()).finish()
+ }
+
+ @Test
+ fun activityLifecycle_finishedAfterResultReturned() {
+ val activity = mock<Activity>()
+ val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.onWaitingForResult(true)
+ controller.onWaitingForResult(false)
+ controller.setActivityFullyVisible(true)
+ callbackCapture.lastValue.onActivityStopped(activity)
+
+ verify(activity).finish()
+ }
+
+ @Test
+ fun activityLifecycle_statePreservedThroughInstanceSave() {
+ val activity = mock<Activity>()
+ val bundle = Bundle(1)
+
+ run {
+ val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.onWaitingForResult(true)
+ callbackCapture.lastValue.onActivitySaveInstanceState(activity, bundle)
+ }
+
+ clearInvocations(activity)
+
+ run {
+ val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ callbackCapture.lastValue.onActivityCreated(activity, bundle)
+ callbackCapture.lastValue.onActivityStopped(activity)
+
+ verify(activity, never()).finish()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
index 5b629b91eb45..48b42d551d60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
@@ -21,7 +21,6 @@ import android.content.Intent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.testKosmos
@@ -62,7 +61,7 @@ class EditWidgetsActivityStarterTest : SysuiTestCase() {
fun activityLaunch_intentIsWellFormed() {
with(kosmos) {
testScope.runTest {
- underTest.startActivity(TEST_PRESELECTED_KEY, shouldOpenWidgetPickerOnStart = true)
+ underTest.startActivity(shouldOpenWidgetPickerOnStart = true)
val captor = argumentCaptor<Intent>()
verify(activityStarter)
@@ -71,8 +70,6 @@ class EditWidgetsActivityStarterTest : SysuiTestCase() {
assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_NEW_TASK).isNotEqualTo(0)
assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_CLEAR_TASK)
.isNotEqualTo(0)
- assertThat(captor.lastValue.extras?.getString(EXTRA_PRESELECTED_KEY))
- .isEqualTo(TEST_PRESELECTED_KEY)
assertThat(
captor.lastValue.extras?.getBoolean(
EditWidgetsActivity.EXTRA_OPEN_WIDGET_PICKER_ON_START
@@ -80,7 +77,7 @@ class EditWidgetsActivityStarterTest : SysuiTestCase() {
)
.isEqualTo(true)
- underTest.startActivity(TEST_PRESELECTED_KEY, shouldOpenWidgetPickerOnStart = false)
+ underTest.startActivity(shouldOpenWidgetPickerOnStart = false)
verify(activityStarter, times(2))
.startActivityDismissingKeyguard(captor.capture(), eq(true), eq(true), any())
@@ -88,8 +85,6 @@ class EditWidgetsActivityStarterTest : SysuiTestCase() {
assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_NEW_TASK).isNotEqualTo(0)
assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_CLEAR_TASK)
.isNotEqualTo(0)
- assertThat(captor.lastValue.extras?.getString(EXTRA_PRESELECTED_KEY))
- .isEqualTo(TEST_PRESELECTED_KEY)
assertThat(
captor.lastValue.extras?.getBoolean(
EditWidgetsActivity.EXTRA_OPEN_WIDGET_PICKER_ON_START
@@ -99,8 +94,4 @@ class EditWidgetsActivityStarterTest : SysuiTestCase() {
}
}
}
-
- companion object {
- const val TEST_PRESELECTED_KEY = "test-key"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index ed214749d6a7..ed214749d6a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
index dd3f991e60b7..dd3f991e60b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
index 383e0fab73ff..383e0fab73ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
index 12cb8a61e0d8..12cb8a61e0d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
index d728517e2000..d728517e2000 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationUtilsTest.java
index 1e802337a9e7..1e802337a9e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationUtilsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
index 98b119ae75c4..98b119ae75c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
index 22ab4994f026..22ab4994f026 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
index c2173c43ad45..c2173c43ad45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
index 3a856a05ac02..3a856a05ac02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
index 6c354ef0966c..6c354ef0966c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/CustomIconCacheTest.kt
index 28e0cffc4f78..28e0cffc4f78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/CustomIconCacheTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 928514657257..928514657257 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
index afa5ceccb6cb..afa5ceccb6cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
index f9c2c6b791f1..f9c2c6b791f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
index e04ce45592b1..e04ce45592b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
index 282ea5ccb5c2..282ea5ccb5c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
index b5c6c538ec9e..b5c6c538ec9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
index 7d197f75b5f9..7d197f75b5f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 844cc1f1f8fa..844cc1f1f8fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AllModelTest.kt
index 5528f6523111..5528f6523111 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AllModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AppAdapterTest.kt
index 56c7c854b69d..56c7c854b69d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AppAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index f1782e8b0569..f1782e8b0569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
index c49867a30dc9..c49867a30dc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
index 281addc053f9..281addc053f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
index d8aac101e84f..d8aac101e84f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/StartActivityData.kt
index 977e3ba899f6..977e3ba899f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/StartActivityData.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
index ec239f64e254..ec239f64e254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index fd4c6810a7fc..fd4c6810a7fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index 86e3481ff263..86e3481ff263 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
index 3bdd5cf8cfe7..3bdd5cf8cfe7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
index 193ce21dcfa0..193ce21dcfa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
index ca33f16b10ac..ca33f16b10ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
index 6092b8c5eb65..6092b8c5eb65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index de2d8529adff..de2d8529adff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
index b3f458821cba..b3f458821cba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
index d2fe68ad8e1a..d2fe68ad8e1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
index 9f4836ad1d9b..9f4836ad1d9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/coroutines/FlowTest.kt
index 23da3f1d3ac0..23da3f1d3ac0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/coroutines/FlowTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 91259a65eff9..3e75cebb1c7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -34,6 +34,7 @@ import android.hardware.face.FaceSensorPropertiesInternal
import android.os.CancellationSignal
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags as AConfigFlags
@@ -55,6 +56,7 @@ import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -77,6 +79,9 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
@@ -90,6 +95,7 @@ import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -136,12 +142,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
@Captor
private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
- private val testDispatcher = kosmos.testDispatcher
+ private val testDispatcher by lazy { kosmos.testDispatcher }
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val testScope = kosmos.testScope
- private val fakeUserRepository = kosmos.fakeUserRepository
- private val fakeExecutor = kosmos.fakeExecutor
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val testScope by lazy { kosmos.testScope }
+ private val fakeUserRepository by lazy { kosmos.fakeUserRepository }
+ private val fakeExecutor by lazy { kosmos.fakeExecutor }
private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
@@ -149,18 +155,19 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
- private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
- private val deviceEntryFingerprintAuthRepository =
+ private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
+ private val deviceEntryFingerprintAuthRepository by lazy {
kosmos.fakeDeviceEntryFingerprintAuthRepository
- private val trustRepository = kosmos.fakeTrustRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val powerInteractor = kosmos.powerInteractor
- private val keyguardInteractor = kosmos.keyguardInteractor
- private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
- private val displayStateInteractor = kosmos.displayStateInteractor
- private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
- private val displayRepository = kosmos.displayRepository
- private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+ }
+ private val trustRepository by lazy { kosmos.fakeTrustRepository }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+ private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
+ private val displayStateInteractor by lazy { kosmos.displayStateInteractor }
+ private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+ private val displayRepository by lazy { kosmos.displayRepository }
+ private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
private lateinit var featureFlags: FakeFeatureFlags
private var wasAuthCancelled = false
@@ -180,9 +187,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
whenever(bypassController.bypassEnabled).thenReturn(true)
underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
- mSetFlagsRule.disableFlags(
- AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
+ }
}
private fun createDeviceEntryFaceAuthRepositoryImpl(
@@ -227,6 +236,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
powerInteractor,
keyguardInteractor,
alternateBouncerInteractor,
+ { kosmos.sceneInteractor },
faceDetectBuffer,
faceAuthBuffer,
keyguardTransitionInteractor,
@@ -547,6 +557,24 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainerEnabled_authenticateDoesNotRunWhenKeyguardIsGoingAway() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.UNDEFINED,
+ value = 0.5f,
+ transitionState = TransitionState.RUNNING
+ ),
+ validateStep = false
+ )
+ runCurrent()
+ }
+ }
+
+ @Test
fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
testScope.runTest {
testGatingCheckForFaceAuth {
@@ -595,6 +623,31 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainer_authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled = true)
+ bouncerRepository.setAlternateVisible(false)
+
+ // launch secure camera
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ keyguardRepository.setKeyguardOccluded(true)
+ kosmos.sceneInteractor.snapToScene(Scenes.Lockscreen, "for-test")
+ runCurrent()
+ assertThat(canFaceAuthRun()).isFalse()
+
+ // but bouncer is shown after that.
+ kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+ )
+ runCurrent()
+
+ assertThat(canFaceAuthRun()).isTrue()
+ }
+
+ @Test
fun authenticateDoesNotRunOnUnsupportedPosture() =
testScope.runTest {
testGatingCheckForFaceAuth {
@@ -841,6 +894,24 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainer_faceDetectDoesNotRunWhenKeyguardGoingAway() =
+ testScope.runTest {
+ testGatingCheckForDetect(sceneContainerEnabled = true) {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.UNDEFINED,
+ value = 0.5f,
+ transitionState = TransitionState.RUNNING
+ ),
+ validateStep = false
+ )
+ runCurrent()
+ }
+ }
+
+ @Test
fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
testScope.runTest {
testGatingCheckForDetect {
@@ -1052,10 +1123,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
private suspend fun TestScope.testGatingCheckForFaceAuth(
+ sceneContainerEnabled: Boolean = false,
gatingCheckModifier: suspend () -> Unit
) {
initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
gatingCheckModifier()
runCurrent()
@@ -1069,7 +1141,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthenticateIsNotCalled()
// flip the gating check back on.
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
assertThat(underTest.canRunFaceAuth.value).isTrue()
faceAuthenticateIsCalled()
@@ -1094,10 +1166,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
private suspend fun TestScope.testGatingCheckForDetect(
+ sceneContainerEnabled: Boolean = false,
gatingCheckModifier: suspend () -> Unit
) {
initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
// This will stop face auth from running but is required to be false for detect.
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
@@ -1145,12 +1218,22 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
}
- private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+ private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
+ sceneContainerEnabled: Boolean = false
+ ) {
fakeExecutor.runAllReady()
verify(faceManager, atLeastOnce())
.addLockoutResetCallback(faceLockoutResetCallback.capture())
trustRepository.setCurrentUserTrusted(false)
- keyguardRepository.setKeyguardGoingAway(false)
+ if (sceneContainerEnabled) {
+ // Keyguard is not going away
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(KeyguardState.OFF, KeyguardState.LOCKSCREEN, value = 1.0f),
+ validateStep = false
+ )
+ } else {
+ keyguardRepository.setKeyguardGoingAway(false)
+ }
powerInteractor.setAwakeForTest()
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 88ba0411b414..296a0fc2eb40 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -88,21 +88,14 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
}
@Test
- fun testWakeUpSetsExitAnimationsRunning() {
- controller.wakeUp()
-
- verify(stateController).setExitAnimationsRunning(true)
- }
-
- @Test
- fun testWakeUpAfterStartWillCancel() {
+ fun testOnWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
- controller.wakeUp()
+ controller.onWakeUp()
// Verify that we cancelled the start animator in favor of the exit
// animator.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 6412276ba34b..3895595aaea6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -62,6 +62,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -324,4 +325,13 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
// enabled.
mController.onViewAttached();
}
+
+ @Test
+ public void destroy_cleansUpState() {
+ mController.destroy();
+ verify(mStateController).removeCallback(any());
+ verify(mAmbientStatusBarViewController).destroy();
+ verify(mComplicationHostViewController).destroy();
+ verify(mLowLightTransitionCoordinator).setLowLightEnterListener(ArgumentMatchers.isNull());
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 89ec3cfe41c5..eda9039c748e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -68,7 +68,6 @@ import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
import com.android.systemui.testKosmos
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -89,7 +88,9 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -115,8 +116,6 @@ class DreamOverlayServiceTest : SysuiTestCase() {
@Mock lateinit var mComplicationComponentFactory: ComplicationComponent.Factory
- @Mock lateinit var mComplicationComponent: ComplicationComponent
-
@Mock lateinit var mComplicationHostViewController: ComplicationHostViewController
@Mock lateinit var mComplicationVisibilityController: ComplicationLayoutEngine
@@ -125,20 +124,12 @@ class DreamOverlayServiceTest : SysuiTestCase() {
lateinit var mDreamComplicationComponentFactory:
com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
- @Mock
- lateinit var mDreamComplicationComponent:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent
-
@Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
@Mock lateinit var mDreamOverlayComponentFactory: DreamOverlayComponent.Factory
- @Mock lateinit var mDreamOverlayComponent: DreamOverlayComponent
-
@Mock lateinit var mAmbientTouchComponentFactory: AmbientTouchComponent.Factory
- @Mock lateinit var mAmbientTouchComponent: AmbientTouchComponent
-
@Mock lateinit var mDreamOverlayContainerView: DreamOverlayContainerView
@Mock lateinit var mDreamOverlayContainerViewController: DreamOverlayContainerViewController
@@ -170,40 +161,100 @@ class DreamOverlayServiceTest : SysuiTestCase() {
private lateinit var communalRepository: FakeCommunalSceneRepository
private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
private lateinit var gestureInteractor: GestureInteractor
+ private lateinit var environmentComponents: EnvironmentComponents
@Captor var mViewCaptor: ArgumentCaptor<View>? = null
private lateinit var mService: DreamOverlayService
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
+ private class EnvironmentComponents(
+ val dreamsComplicationComponent:
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent,
+ val dreamOverlayComponent: DreamOverlayComponent,
+ val complicationComponent: ComplicationComponent,
+ val ambientTouchComponent: AmbientTouchComponent,
+ ) {
+ fun clearInvocations() {
+ clearInvocations(
+ dreamsComplicationComponent,
+ dreamOverlayComponent,
+ complicationComponent,
+ ambientTouchComponent
+ )
+ }
- lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner)
- bouncerRepository = kosmos.fakeKeyguardBouncerRepository
- communalRepository = kosmos.fakeCommunalSceneRepository
- gestureInteractor = spy(kosmos.gestureInteractor)
+ fun verifyNoMoreInteractions() {
+ Mockito.verifyNoMoreInteractions(
+ dreamsComplicationComponent,
+ dreamOverlayComponent,
+ complicationComponent,
+ ambientTouchComponent
+ )
+ }
+ }
- whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController())
+ private fun setupComponentFactories(
+ dreamComplicationComponentFactory:
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory,
+ dreamOverlayComponentFactory: DreamOverlayComponent.Factory,
+ complicationComponentFactory: ComplicationComponent.Factory,
+ ambientTouchComponentFactory: AmbientTouchComponent.Factory
+ ): EnvironmentComponents {
+ val dreamOverlayComponent = mock<DreamOverlayComponent>()
+ whenever(dreamOverlayComponent.getDreamOverlayContainerViewController())
.thenReturn(mDreamOverlayContainerViewController)
- whenever(mComplicationComponent.getComplicationHostViewController())
+
+ val complicationComponent = mock<ComplicationComponent>()
+ whenever(complicationComponent.getComplicationHostViewController())
.thenReturn(mComplicationHostViewController)
whenever(mLifecycleOwner.registry).thenReturn(lifecycleRegistry)
mCommunalInteractor = Mockito.spy(kosmos.communalInteractor)
- whenever(mComplicationComponentFactory.create(any(), any(), any(), any()))
- .thenReturn(mComplicationComponent)
- whenever(mComplicationComponent.getVisibilityController())
+ whenever(complicationComponentFactory.create(any(), any(), any(), any()))
+ .thenReturn(complicationComponent)
+ whenever(complicationComponent.getVisibilityController())
.thenReturn(mComplicationVisibilityController)
- whenever(mDreamComplicationComponent.getHideComplicationTouchHandler())
+
+ val dreamComplicationComponent =
+ mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent>()
+ whenever(dreamComplicationComponent.getHideComplicationTouchHandler())
.thenReturn(mHideComplicationTouchHandler)
- whenever(mDreamComplicationComponentFactory.create(any(), any()))
- .thenReturn(mDreamComplicationComponent)
- whenever(mDreamOverlayComponentFactory.create(any(), any(), any()))
- .thenReturn(mDreamOverlayComponent)
- whenever(mAmbientTouchComponentFactory.create(any(), any()))
- .thenReturn(mAmbientTouchComponent)
- whenever(mAmbientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
+ whenever(dreamComplicationComponentFactory.create(any(), any()))
+ .thenReturn(dreamComplicationComponent)
+
+ whenever(dreamOverlayComponentFactory.create(any(), any(), any()))
+ .thenReturn(dreamOverlayComponent)
+
+ val ambientTouchComponent = mock<AmbientTouchComponent>()
+ whenever(ambientTouchComponentFactory.create(any(), any()))
+ .thenReturn(ambientTouchComponent)
+ whenever(ambientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
+
+ return EnvironmentComponents(
+ dreamComplicationComponent,
+ dreamOverlayComponent,
+ complicationComponent,
+ ambientTouchComponent
+ )
+ }
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner)
+ bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ communalRepository = kosmos.fakeCommunalSceneRepository
+ gestureInteractor = spy(kosmos.gestureInteractor)
+
+ environmentComponents =
+ setupComponentFactories(
+ mDreamComplicationComponentFactory,
+ mDreamOverlayComponentFactory,
+ mComplicationComponentFactory,
+ mAmbientTouchComponentFactory
+ )
+
whenever(mDreamOverlayContainerViewController.containerView)
.thenReturn(mDreamOverlayContainerView)
whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
@@ -570,9 +621,8 @@ class DreamOverlayServiceTest : SysuiTestCase() {
// Assert that the overlay is not showing complications.
assertThat(mService.shouldShowComplications()).isFalse()
- Mockito.clearInvocations(mDreamOverlayComponent)
- Mockito.clearInvocations(mAmbientTouchComponent)
- Mockito.clearInvocations(mWindowManager)
+ environmentComponents.clearInvocations()
+ clearInvocations(mWindowManager)
// New dream starting with dream complications showing. Note that when a new dream is
// binding to the dream overlay service, it receives the same instance of IBinder as the
@@ -594,8 +644,11 @@ class DreamOverlayServiceTest : SysuiTestCase() {
// Verify that new instances of overlay container view controller and overlay touch monitor
// are created.
- verify(mDreamOverlayComponent).getDreamOverlayContainerViewController()
- verify(mAmbientTouchComponent).getTouchMonitor()
+ verify(environmentComponents.dreamOverlayComponent).getDreamOverlayContainerViewController()
+ verify(environmentComponents.ambientTouchComponent).getTouchMonitor()
+
+ // Verify DreamOverlayContainerViewController is destroyed.
+ verify(mDreamOverlayContainerViewController).destroy()
}
@Test
@@ -611,14 +664,14 @@ class DreamOverlayServiceTest : SysuiTestCase() {
)
mMainExecutor.runAllReady()
mService.onWakeUp()
- verify(mDreamOverlayContainerViewController).wakeUp()
+ verify(mDreamOverlayContainerViewController).onWakeUp()
verify(mDreamOverlayCallbackController).onWakeUp()
}
@Test
fun testWakeUpBeforeStartDoesNothing() {
mService.onWakeUp()
- verify(mDreamOverlayContainerViewController, Mockito.never()).wakeUp()
+ verify(mDreamOverlayContainerViewController, Mockito.never()).onWakeUp()
}
@Test
@@ -1002,6 +1055,34 @@ class DreamOverlayServiceTest : SysuiTestCase() {
.isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
}
+ @Test
+ fun testComponentsRecreatedBetweenDreams() {
+ clearInvocations(
+ mDreamComplicationComponentFactory,
+ mDreamOverlayComponentFactory,
+ mComplicationComponentFactory,
+ mAmbientTouchComponentFactory
+ )
+
+ mService.onEndDream()
+
+ setupComponentFactories(
+ mDreamComplicationComponentFactory,
+ mDreamOverlayComponentFactory,
+ mComplicationComponentFactory,
+ mAmbientTouchComponentFactory
+ )
+
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ environmentComponents.verifyNoMoreInteractions()
+ }
+
internal class FakeLifecycleRegistry(provider: LifecycleOwner) : LifecycleRegistry(provider) {
val mLifecycles: MutableList<State> = ArrayList()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index f82beff11d8f..50b727c3fed9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
@@ -105,6 +106,19 @@ class ContextualEducationRepositoryTest : SysuiTestCase() {
assertThat(model).isEqualTo(newModel)
}
+ @Test
+ fun eduDeviceConnectionTimeDataChangedOnUpdate() =
+ testScope.runTest {
+ val newModel =
+ EduDeviceConnectionTime(
+ keyboardFirstConnectionTime = kosmos.fakeEduClock.instant(),
+ touchpadFirstConnectionTime = kosmos.fakeEduClock.instant(),
+ )
+ underTest.updateEduDeviceConnectionTime { newModel }
+ val model by collectLastValue(underTest.readEduDeviceConnectionTime())
+ assertThat(model).isEqualTo(newModel)
+ }
+
/** Test context which allows overriding getFilesDir path */
private class TestContext(context: Context, private val folder: File) :
SysuiTestableContext(context) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 23f923a6fb09..3aed79f5915f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.education.domain.interactor
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,10 +27,15 @@ import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.data.repository.touchpadRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -38,16 +44,23 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val contextualEduInteractor = kosmos.contextualEducationInteractor
+ private val touchpadRepository = kosmos.touchpadRepository
+ private val keyboardRepository = kosmos.keyboardRepository
+ private val userRepository = kosmos.fakeUserRepository
+
private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
private val eduClock = kosmos.fakeEduClock
@Before
fun setup() {
underTest.start()
+ contextualEduInteractor.start()
+ userRepository.setUserInfos(USER_INFOS)
}
@Test
@@ -67,7 +80,6 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
}
@Test
- @kotlinx.coroutines.ExperimentalCoroutinesApi
fun newEducationNotificationOn2ndEducation() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
@@ -115,10 +127,103 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
)
}
+ @Test
+ fun newTouchpadConnectionTimeOnFirstTouchpadConnected() =
+ testScope.runTest {
+ setIsAnyTouchpadConnected(true)
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(eduClock.instant())
+ }
+
+ @Test
+ fun unchangedTouchpadConnectionTimeOnSecondConnection() =
+ testScope.runTest {
+ val firstConnectionTime = eduClock.instant()
+ setIsAnyTouchpadConnected(true)
+ setIsAnyTouchpadConnected(false)
+
+ eduClock.offset(1.hours)
+ setIsAnyTouchpadConnected(true)
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(firstConnectionTime)
+ }
+
+ @Test
+ fun newTouchpadConnectionTimeOnUserChanged() =
+ testScope.runTest {
+ // Touchpad connected for user 0
+ setIsAnyTouchpadConnected(true)
+
+ // Change user
+ eduClock.offset(1.hours)
+ val newUserFirstConnectionTime = eduClock.instant()
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ runCurrent()
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
+ }
+
+ @Test
+ fun newKeyboardConnectionTimeOnKeyboardConnected() =
+ testScope.runTest {
+ setIsAnyKeyboardConnected(true)
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(eduClock.instant())
+ }
+
+ @Test
+ fun unchangedKeyboardConnectionTimeOnSecondConnection() =
+ testScope.runTest {
+ val firstConnectionTime = eduClock.instant()
+ setIsAnyKeyboardConnected(true)
+ setIsAnyKeyboardConnected(false)
+
+ eduClock.offset(1.hours)
+ setIsAnyKeyboardConnected(true)
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(firstConnectionTime)
+ }
+
+ @Test
+ fun newKeyboardConnectionTimeOnUserChanged() =
+ testScope.runTest {
+ // Keyboard connected for user 0
+ setIsAnyKeyboardConnected(true)
+
+ // Change user
+ eduClock.offset(1.hours)
+ val newUserFirstConnectionTime = eduClock.instant()
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ runCurrent()
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
+ }
+
private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
// Increment max number of signal to try triggering education
for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
contextualEduInteractor.incrementSignalCount(gestureType)
}
}
+
+ private fun TestScope.setIsAnyTouchpadConnected(isConnected: Boolean) {
+ touchpadRepository.setIsAnyTouchpadConnected(isConnected)
+ runCurrent()
+ }
+
+ private fun TestScope.setIsAnyKeyboardConnected(isConnected: Boolean) {
+ keyboardRepository.setIsAnyKeyboardConnected(isConnected)
+ runCurrent()
+ }
+
+ companion object {
+ private val USER_INFOS =
+ listOf(
+ UserInfo(101, "Second User", 0),
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 3a28471b5f9a..9bcc19defbd5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -369,4 +369,18 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
invokeOnCallback { it.onStrongAuthStateChanged(0) }
assertThat(shouldUpdateIndicatorVisibility).isTrue()
}
+
+ @Test
+ fun isLockedOut_initialStateFalse() =
+ testScope.runTest {
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+ assertThat(underTest.isLockedOut.value).isEqualTo(false)
+ }
+
+ @Test
+ fun isLockedOut_initialStateTrue() =
+ testScope.runTest {
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+ assertThat(underTest.isLockedOut.value).isEqualTo(true)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 783e3b5ba810..ee4a0d2d4e75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -150,13 +150,13 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
@DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
+ fun testTransitionToLockscreen_onWake_canDream_glanceableHubAvailable() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
kosmos.setCommunalAvailable(true)
runCurrent()
- powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ powerInteractor.setAwakeForTest()
runCurrent()
// If dreaming is possible and communal is available, then we should transition to
@@ -170,14 +170,14 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
- fun testTransitionToLockscreen_onPowerButtonPress_canDream_ktfRefactor() =
+ fun testTransitionToLockscreen_onWake_canDream_ktfRefactor() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
kosmos.setCommunalAvailable(true)
runCurrent()
clearInvocations(kosmos.fakeCommunalSceneRepository)
- powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ powerInteractor.setAwakeForTest()
runCurrent()
// If dreaming is possible and communal is available, then we should transition to
@@ -188,13 +188,13 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testTransitionToLockscreen_onPowerButtonPress_canNotDream_glanceableHubAvailable() =
+ fun testTransitionToLockscreen_onWake_canNotDream_glanceableHubAvailable() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
kosmos.setCommunalAvailable(true)
runCurrent()
- powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ powerInteractor.setAwakeForTest()
runCurrent()
// If dreaming is NOT possible but communal is available, then we should transition to
@@ -208,13 +208,13 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testTransitionToLockscreen_onPowerButtonPress_canNDream_glanceableHubNotAvailable() =
+ fun testTransitionToLockscreen_onWake_canNDream_glanceableHubNotAvailable() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
kosmos.setCommunalAvailable(false)
runCurrent()
- powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ powerInteractor.setAwakeForTest()
runCurrent()
// If dreaming is possible but communal is NOT available, then we should transition to
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index 2b2c121fb79a..aee72de22a24 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -25,8 +25,11 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -260,6 +263,23 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
}
@Test
+ fun triggersFaceAuthWhenLockscreenIsClicked() =
+ testScope.runTest {
+ collectLastValue(underTest.isMenuVisible)
+ runCurrent()
+ kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true
+
+ underTest.onClick(100.0f, 100.0f)
+ runCurrent()
+
+ val runningAuthRequest =
+ kosmos.fakeDeviceEntryFaceAuthRepository.runningAuthRequest.value
+ assertThat(runningAuthRequest?.first)
+ .isEqualTo(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED)
+ assertThat(runningAuthRequest?.second).isEqualTo(true)
+ }
+
+ @Test
fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
testScope.runTest {
val isMenuVisible by collectLastValue(underTest.isMenuVisible)
@@ -302,6 +322,7 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
broadcastDispatcher = fakeBroadcastDispatcher,
accessibilityManager = kosmos.accessibilityManagerWrapper,
pulsingGestureListener = kosmos.pulsingGestureListener,
+ faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
)
setUpState()
}
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 a40746859a9a..3e1f4f6da5e4 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
@@ -504,6 +504,45 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
+ @DisableSceneContainer
+ fun alphaFromShadeExpansion_doesNotEmitWhenLockscreenToDreamTransitionRunning() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ val alpha by collectLastValue(underTest.alpha(viewState))
+ shadeTestUtil.setQsExpansion(0f)
+
+ assertThat(alpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ transitionState = TransitionState.STARTED,
+ value = 0f,
+ ),
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 0.1f,
+ ),
+ ),
+ testScope,
+ )
+
+ val alphaBeforeExpansion = alpha
+ shadeTestUtil.setQsExpansion(0.5f)
+ // Alpha should remain unchanged instead of being affected by expansion.
+ assertThat(alpha).isEqualTo(alphaBeforeExpansion)
+ }
+
+ @Test
fun alpha_shadeClosedOverLockscreen_isOne() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha(viewState))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt
deleted file mode 100644
index f6f58c9bdcf2..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.lifecycle
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BaseActivatableTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private val underTest = FakeActivatable()
-
- @Test
- fun activate() =
- testScope.runTest {
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(0)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- underTest.activateIn(testScope)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(0)
- }
-
- @Test
- fun activate_andCancel() =
- testScope.runTest {
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(0)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- val job = Job()
- underTest.activateIn(testScope, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(1)
- }
-
- @Test
- fun activate_afterCancellation() =
- testScope.runTest {
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(0)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- val job = Job()
- underTest.activateIn(testScope, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(1)
-
- underTest.activateIn(testScope)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(2)
- assertThat(underTest.cancellationCount).isEqualTo(1)
- }
-
- @Test(expected = IllegalStateException::class)
- fun activate_whileActive_throws() =
- testScope.runTest {
- assertThat(underTest.isActive).isFalse()
- assertThat(underTest.activationCount).isEqualTo(0)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- underTest.activateIn(testScope)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(underTest.activationCount).isEqualTo(1)
- assertThat(underTest.cancellationCount).isEqualTo(0)
-
- underTest.activateIn(testScope)
- runCurrent()
- }
-
- @Test
- fun addChild_beforeActive_activatesChildrenOnceActivated() =
- testScope.runTest {
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- assertThat(underTest.isActive).isFalse()
- underTest.addChild(child1)
- underTest.addChild(child2)
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.activateIn(this)
- runCurrent()
-
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
- }
-
- @Test
- fun addChild_whileActive_activatesChildrenImmediately() =
- testScope.runTest {
- underTest.activateIn(this)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
-
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.addChild(child1)
- underTest.addChild(child2)
- runCurrent()
-
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
- }
-
- @Test
- fun addChild_afterCancellation_doesNotActivateChildren() =
- testScope.runTest {
- val job = Job()
- underTest.activateIn(this, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
-
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.addChild(child1)
- underTest.addChild(child2)
- runCurrent()
-
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
- }
-
- @Test
- fun activate_cancellation_cancelsCurrentChildren() =
- testScope.runTest {
- val job = Job()
- underTest.activateIn(this, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
-
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.addChild(child1)
- underTest.addChild(child2)
- runCurrent()
-
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
- }
-
- @Test
- fun activate_afterCancellation_reactivatesCurrentChildren() =
- testScope.runTest {
- val job = Job()
- underTest.activateIn(this, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
-
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.addChild(child1)
- underTest.addChild(child2)
- runCurrent()
-
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.activateIn(this)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
- }
-
- @Test
- fun removeChild_beforeActive_neverActivatesChild() =
- testScope.runTest {
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- assertThat(underTest.isActive).isFalse()
- underTest.addChild(child1)
- underTest.addChild(child2)
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
- }
-
- @Test
- fun removeChild_whileActive_cancelsChild() =
- testScope.runTest {
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- assertThat(underTest.isActive).isFalse()
- underTest.addChild(child1)
- underTest.addChild(child2)
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.activateIn(this)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
-
- underTest.removeChild(child1)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isTrue()
- }
-
- @Test
- fun removeChild_afterCancellation_doesNotReactivateChildren() =
- testScope.runTest {
- val child1 = FakeActivatable()
- val child2 = FakeActivatable()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- assertThat(underTest.isActive).isFalse()
- underTest.addChild(child1)
- underTest.addChild(child2)
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- val job = Job()
- underTest.activateIn(this, context = job)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isTrue()
- assertThat(child2.isActive).isTrue()
-
- job.cancel()
- runCurrent()
- assertThat(underTest.isActive).isFalse()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isFalse()
-
- underTest.removeChild(child1)
- underTest.activateIn(this)
- runCurrent()
- assertThat(underTest.isActive).isTrue()
- assertThat(child1.isActive).isFalse()
- assertThat(child2.isActive).isTrue()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt
new file mode 100644
index 000000000000..81b91802ec28
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.lifecycle
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ExclusiveActivatableTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val underTest = FakeActivatable()
+
+ @Test
+ fun activate() =
+ testScope.runTest {
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+ }
+
+ @Test
+ fun activate_andCancel() =
+ testScope.runTest {
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ val job = Job()
+ underTest.activateIn(testScope, context = job)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ job.cancel()
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+ }
+
+ @Test
+ fun activate_afterCancellation() =
+ testScope.runTest {
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ val job = Job()
+ underTest.activateIn(testScope, context = job)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ job.cancel()
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(2)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun activate_whileActive_throws() =
+ testScope.runTest {
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
new file mode 100644
index 000000000000..8c9c527bd6fd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.lifecycle
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HydratorTest : SysuiTestCase() {
+
+ @get:Rule val composeRule = createComposeRule()
+
+ @Test
+ fun hydratedStateOf() {
+ val keepAliveMutable = mutableStateOf(true)
+ val upstreamStateFlow = MutableStateFlow(true)
+ val upstreamFlow = upstreamStateFlow.map { !it }
+ composeRule.setContent {
+ val keepAlive by keepAliveMutable
+ if (keepAlive) {
+ val viewModel = rememberViewModel {
+ FakeSysUiViewModel(
+ upstreamFlow = upstreamFlow,
+ upstreamStateFlow = upstreamStateFlow,
+ )
+ }
+
+ Column {
+ Text(
+ "upstreamStateFlow=${viewModel.stateBackedByStateFlow}",
+ Modifier.testTag("upstreamStateFlow")
+ )
+ Text(
+ "upstreamFlow=${viewModel.stateBackedByFlow}",
+ Modifier.testTag("upstreamFlow")
+ )
+ }
+ }
+ }
+
+ composeRule.waitForIdle()
+ composeRule
+ .onNode(hasTestTag("upstreamStateFlow"))
+ .assertTextEquals("upstreamStateFlow=true")
+ composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=false")
+
+ composeRule.runOnUiThread { upstreamStateFlow.value = false }
+ composeRule.waitForIdle()
+ composeRule
+ .onNode(hasTestTag("upstreamStateFlow"))
+ .assertTextEquals("upstreamStateFlow=false")
+ composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=true")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
new file mode 100644
index 000000000000..22e589637432
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -0,0 +1,399 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.Notification
+import android.app.Notification.MediaStyle
+import android.app.PendingIntent
+import android.app.statusBarManager
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.media.AudioAttributes
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+import android.service.notification.StatusBarNotification
+import androidx.media.utils.MediaConstants
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val KEY = "KEY"
+private const val PACKAGE_NAME = "com.example.app"
+private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
+private const val APP_NAME = "SystemUI"
+private const val SESSION_ARTIST = "artist"
+private const val SESSION_TITLE = "title"
+private const val SESSION_EMPTY_TITLE = ""
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaDataLoaderTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val testDispatcher = kosmos.testDispatcher
+ private val statusBarManager = kosmos.statusBarManager
+ private val mediaController = mock<MediaController>()
+ private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+ private val mediaFlags = kosmos.mediaFlags
+ private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+ private val session = MediaSession(context, "MediaDataLoaderTestSession")
+ private val metadataBuilder =
+ MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+ }
+
+ private val underTest: MediaDataLoader =
+ MediaDataLoader(
+ context,
+ testDispatcher,
+ testScope,
+ kosmos.activityStarter,
+ mediaControllerFactory,
+ mediaFlags,
+ kosmos.imageLoader,
+ statusBarManager
+ )
+
+ @Before
+ fun setUp() {
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
+ mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+ }
+
+ @Test
+ fun loadMediaData_returnsMediaData() =
+ testScope.runTest {
+ val song = "THIS_IS_A_SONG"
+ val artist = "THIS_IS_AN_ARTIST"
+ val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+ whenever(mediaController.playbackState)
+ .thenReturn(
+ PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 12, 1.0f).build()
+ )
+ whenever(mediaController.playbackInfo)
+ .thenReturn(
+ MediaController.PlaybackInfo(
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+ 0,
+ 0,
+ 0,
+ AudioAttributes.Builder().build(),
+ null
+ )
+ )
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, song)
+ .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
+ .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt)
+ .putLong(
+ MediaConstants.METADATA_KEY_IS_EXPLICIT,
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ )
+ .build()
+ )
+
+ val result = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result).isNotNull()
+ assertThat(result?.appIcon).isNotNull()
+ assertThat(result?.appIcon?.resId).isEqualTo(android.R.drawable.ic_media_pause)
+ assertThat(result?.artist).isEqualTo(artist)
+ assertThat(result?.song).isEqualTo(song)
+ assertThat(result?.artworkIcon).isNotNull()
+ assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(albumArt.width)
+ assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(albumArt.height)
+ assertThat(result?.token).isEqualTo(session.sessionToken)
+ assertThat(result?.device).isNull()
+ assertThat(result?.playbackLocation).isEqualTo(MediaData.PLAYBACK_LOCAL)
+ assertThat(result?.isPlaying).isTrue()
+ assertThat(result?.isExplicit).isTrue()
+ assertThat(result?.resumeAction).isNull()
+ assertThat(result?.resumeProgress).isNull()
+ }
+
+ @Test
+ fun loadMediaDataForResumption_returnsMediaData() =
+ testScope.runTest {
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
+
+ val song = "THIS_IS_A_SONG"
+ val artist = "THIS_IS_AN_ARTIST"
+ val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+ val extras = Bundle()
+ extras.putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ )
+ extras.putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.3)
+ extras.putLong(
+ MediaConstants.METADATA_KEY_IS_EXPLICIT,
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ )
+
+ val description =
+ MediaDescription.Builder()
+ .setTitle(song)
+ .setSubtitle(artist)
+ .setIconBitmap(albumArt)
+ .setExtras(extras)
+ .build()
+
+ val intent =
+ PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+
+ val result =
+ underTest.loadMediaDataForResumption(
+ 0,
+ description,
+ Runnable {},
+ null,
+ session.sessionToken,
+ APP_NAME,
+ intent,
+ PACKAGE_NAME
+ )
+ assertThat(result).isNotNull()
+ assertThat(result?.appName).isEqualTo(APP_NAME)
+ assertThat(result?.song).isEqualTo(song)
+ assertThat(result?.artist).isEqualTo(artist)
+ assertThat(result?.artworkIcon).isNotNull()
+ assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(100)
+ assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(100)
+ assertThat(result?.token).isEqualTo(session.sessionToken)
+ assertThat(result?.clickIntent).isEqualTo(intent)
+ assertThat(result?.isExplicit).isTrue()
+ assertThat(result?.resumeProgress).isEqualTo(0.3)
+ }
+
+ @Test
+ fun loadMediaData_songNameFallbacks() =
+ testScope.runTest {
+ // Check ordering of Song resolution:
+ // DISPLAY_TITLE > TITLE > notification TITLE > notification TITLE_BIG
+
+ // DISPLAY_TITLE
+ whenever(mediaController.metadata)
+ .thenReturn(
+ MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "title1")
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+ .build()
+ )
+ val result1 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result1?.song).isEqualTo("title1")
+
+ // TITLE
+ whenever(mediaController.metadata)
+ .thenReturn(
+ MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+ .build()
+ )
+ val result2 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result2?.song).isEqualTo("title2")
+
+ // notification TITLE
+ val notif =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setContentTitle("notiftitle")
+ }
+ build()
+ }
+ whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+ val result3 = underTest.loadMediaData(KEY, notif)
+ assertThat(result3?.song).isEqualTo("notiftitle")
+
+ // Final fallback
+ whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+ val result4 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result4?.song)
+ .isEqualTo(context.getString(R.string.controls_media_empty_title, result4?.appName))
+ }
+
+ @Test
+ fun loadMediaData_emptyTitle_hasPlaceholder() =
+ testScope.runTest {
+ val packageManager = mock<PackageManager>()
+ context.setMockPackageManager(packageManager)
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+ .build()
+ )
+
+ val result = underTest.loadMediaData(KEY, createMediaNotification())
+
+ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+ assertThat(result).isNotNull()
+ assertThat(result?.song).isEqualTo(placeholderTitle)
+ }
+
+ @Test
+ fun loadMediaData_emptyMetadata_usesNotificationTitle() =
+ testScope.runTest {
+ val packageManager = mock<PackageManager>()
+ context.setMockPackageManager(packageManager)
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE)
+ .build()
+ )
+ val mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setContentTitle(SESSION_TITLE)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, mediaNotification)
+
+ assertThat(result).isNotNull()
+ assertThat(result?.song).isEqualTo(SESSION_TITLE)
+ }
+
+ @Test
+ fun loadMediaData_badArtwork_isNotUsed() =
+ testScope.runTest {
+ val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ val mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setLargeIcon(artwork)
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, mediaNotification)
+
+ assertThat(result).isNotNull()
+ }
+
+ @Test
+ fun loadMediaData_invalidTokenNoCrash() =
+ testScope.runTest {
+ val bundle = Bundle()
+ // wrong data type
+ bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle())
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.addExtras(bundle)
+ it.setStyle(
+ MediaStyle().apply { setRemotePlaybackInfo("Remote device", 0, null) }
+ )
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, rcn)
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun testLoadMediaDataInBg_invalidMediaRemoteIntentNoCrash() =
+ testScope.runTest {
+ val bundle = Bundle()
+ // wrong data type
+ bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle())
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.addExtras(bundle)
+ it.setStyle(
+ MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ }
+ )
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, rcn)
+ assertThat(result).isNotNull()
+ }
+
+ private fun createMediaNotification(
+ mediaSession: MediaSession? = session,
+ applicationInfo: ApplicationInfo? = null
+ ): StatusBarNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(mediaSession?.sessionToken) })
+ if (applicationInfo != null) {
+ val bundle = Bundle()
+ bundle.putParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ applicationInfo
+ )
+ it.addExtras(bundle)
+ }
+ }
+ build()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 59992650cfc7..768fbca90b56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -31,6 +31,8 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.fgsManagerController
import com.android.systemui.res.R
import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
@@ -140,6 +142,42 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() {
}
}
+ @Test
+ fun statusBarState_followsController() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val statusBarState by collectLastValue(underTest.statusBarState)
+ runCurrent()
+
+ sysuiStatusBarStateController.setState(StatusBarState.SHADE)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
+
+ sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+ sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
+ }
+ }
+
+ @Test
+ fun statusBarState_changesEarlyIfUpcomingStateIsKeyguard() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val statusBarState by collectLastValue(underTest.statusBarState)
+
+ sysuiStatusBarStateController.setState(StatusBarState.SHADE)
+ sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE_LOCKED)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
+
+ sysuiStatusBarStateController.setUpcomingState(StatusBarState.KEYGUARD)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+ sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+ }
+ }
+
private inline fun TestScope.testWithinLifecycle(
crossinline block: suspend TestScope.() -> TestResult
): TestResult {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
index 1118a6150fcc..e2149d907688 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
@@ -26,7 +26,6 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.shared.model.MediaData
@@ -82,7 +81,6 @@ class QuickSettingsSceneContentViewModelTest : SysuiTestCase() {
footerActionsController = footerActionsController,
mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
)
- underTest.activateIn(testScope)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 163b9b0959a8..c63381687f18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -495,7 +495,9 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private fun getCurrentSceneInUi(): SceneKey {
return when (val state = transitionState.value) {
is ObservableTransitionState.Idle -> state.currentScene
- is ObservableTransitionState.Transition -> state.fromScene
+ is ObservableTransitionState.Transition.ChangeCurrentScene -> state.fromScene
+ is ObservableTransitionState.Transition.ShowOrHideOverlay -> state.currentScene
+ is ObservableTransitionState.Transition.ReplaceOverlay -> state.currentScene
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
index 206d3ac67778..dd4432dd9797 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
@@ -55,7 +55,6 @@ class SceneActionsViewModelTest : SysuiTestCase() {
testScope.runTest {
val actions by collectLastValue(underTest.actions)
- assertThat(underTest.isActive).isFalse()
assertThat(actions).isEmpty()
}
@@ -66,7 +65,6 @@ class SceneActionsViewModelTest : SysuiTestCase() {
underTest.activateIn(testScope)
runCurrent()
- assertThat(underTest.isActive).isTrue()
assertThat(actions).isEmpty()
}
@@ -76,7 +74,6 @@ class SceneActionsViewModelTest : SysuiTestCase() {
val actions by collectLastValue(underTest.actions)
underTest.activateIn(testScope)
runCurrent()
- assertThat(underTest.isActive).isTrue()
val expected1 =
mapOf(
@@ -116,7 +113,6 @@ class SceneActionsViewModelTest : SysuiTestCase() {
job.cancel()
runCurrent()
- assertThat(underTest.isActive).isFalse()
assertThat(actions).isEmpty()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
new file mode 100644
index 000000000000..a310ef44cf35
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
+import com.android.systemui.statusbar.notification.row.shared.IconModel
+import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
+class EnRouteViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val repository = kosmos.fakeNotificationRowRepository
+
+ private var contentModel: EnRouteContentModel?
+ get() = repository.richOngoingContentModel.value as? EnRouteContentModel
+ set(value) {
+ repository.richOngoingContentModel.value = value
+ }
+
+ private lateinit var underTest: EnRouteViewModel
+
+ @Before
+ fun setup() {
+ underTest = kosmos.getEnRouteViewModel(repository)
+ }
+
+ @Test
+ fun viewModelShowsContent() =
+ testScope.runTest {
+ val title by collectLastValue(underTest.title)
+ val text by collectLastValue(underTest.text)
+ contentModel =
+ exampleEnRouteContent(
+ title = "Example EnRoute Title",
+ text = "Example EnRoute Text",
+ )
+ assertThat(title).isEqualTo("Example EnRoute Title")
+ assertThat(text).isEqualTo("Example EnRoute Text")
+ }
+
+ private fun exampleEnRouteContent(
+ icon: IconModel = mock(),
+ title: CharSequence = "example text",
+ text: CharSequence = "example title",
+ ) =
+ EnRouteContentModel(
+ smallIcon = icon,
+ title = title,
+ text = text,
+ )
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
index 789a47304ecf..f920b187e7e5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
@@ -4,6 +4,7 @@ import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.annotation.VisibleForTesting
+import androidx.core.text.util.LocalePreferences
typealias WeatherTouchAction = (View) -> Unit
@@ -54,12 +55,35 @@ data class WeatherData(
}
}
- private fun readIntFromBundle(extras: Bundle, key: String): Int? =
+ private fun readIntFromBundle(extras: Bundle, key: String): Int? {
try {
- extras.getString(key)?.toInt()
+ return extras.getString(key)?.toInt()
} catch (e: Exception) {
- null
+ return null
}
+ }
+
+ fun getPlaceholderWeatherData(): WeatherData {
+ return getPlaceholderWeatherData(
+ LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
+ )
+ }
+
+ private const val DESCRIPTION_PLACEHODLER = ""
+ private const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
+ private const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
+ private val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
+
+ fun getPlaceholderWeatherData(useCelsius: Boolean): WeatherData {
+ return WeatherData(
+ description = DESCRIPTION_PLACEHODLER,
+ state = WEATHERICON_PLACEHOLDER,
+ temperature =
+ if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
+ else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
+ useCelsius = useCelsius,
+ )
+ }
}
// Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
new file mode 100644
index 000000000000..59cfeccbeb36
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:minHeight="@*android:dimen/notification_headerless_min_height"
+ android:tag="enroute"
+ >
+
+ <include layout="@*android:layout/notification_template_material_base" />
+
+</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index fc6d20e11d3b..0c11d2fa1d11 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -27,6 +27,8 @@
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">true</bool>
+ <bool name="config_use_large_screen_shade_header">true</bool>
+
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">2</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index b4383156dc71..c594f1cd9313 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -35,8 +35,6 @@
<!-- How many lines to show in the security footer -->
<integer name="qs_security_footer_maxLines">1</integer>
- <bool name="config_use_large_screen_shade_header">true</bool>
-
<!-- Whether to show bottom sheets edge to edge -->
<bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 18b70731700a..fd943d0a5414 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1869,6 +1869,8 @@
<!-- Text displayed indicating that the user is connected to a satellite signal. -->
<string name="satellite_connected_carrier_text">Satellite SOS</string>
+ <!-- Text displayed indicating that the user might be able to use satellite SOS. -->
+ <string name="satellite_emergency_only_carrier_text">Emergency calls or SOS</string>
<!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_managed_profile">Work profile</string>
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json
new file mode 100644
index 000000000000..fe996b7d4b64
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json
@@ -0,0 +1,81 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 3,
+ "identityHash": "02e2da2d36e6955200edd5fb49e63c72",
+ "entities": [
+ {
+ "tableName": "communal_widget_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "widgetId",
+ "columnName": "widget_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentName",
+ "columnName": "component_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemId",
+ "columnName": "item_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userSerialNumber",
+ "columnName": "user_serial_number",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "-1"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ },
+ {
+ "tableName": "communal_item_rank_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02e2da2d36e6955200edd5fb49e63c72')"
+ ]
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index fbe139930e11..e68da09b26d1 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -48,6 +48,7 @@ android_library {
"src/**/*.kt",
"src/**/*.aidl",
":wm_shell-aidls",
+ ":wm_shell-shared-aidls",
":wm_shell_util-sources",
],
static_libs: [
@@ -69,6 +70,7 @@ android_library {
"dagger2",
"jsr330",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:msdl",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4ef1f93481f7..121577e438b0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -342,8 +342,7 @@ public class QuickStepContract {
// the keyguard)
if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
|| (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0
- || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0
- || (sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
+ || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) {
return false;
}
if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 5dcf1618ed6b..c1eae2e53a44 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -477,6 +477,12 @@ constructor(
smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
}
+ fun setFallbackWeatherData(data: WeatherData) {
+ if (weatherData != null) return
+ weatherData = data
+ clock?.run { events.onWeatherDataChanged(data) }
+ }
+
/**
* Sets this clock as showing in a secondary display.
*
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index bf905dbe5543..7efe2dde1320 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -344,7 +344,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
R.dimen.keyguard_security_container_padding_top), getPaddingRight(),
getPaddingBottom());
setBackgroundColor(Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.materialColorSurface));
+ com.android.internal.R.attr.materialColorSurfaceDim));
}
void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
@@ -808,7 +808,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
void reloadColors() {
mViewMode.reloadColors();
setBackgroundColor(Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.materialColorSurface));
+ com.android.internal.R.attr.materialColorSurfaceDim));
}
/** Handles density or font scale changes. */
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java
new file mode 100644
index 000000000000..c94487848b81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for tracking the current accessibility gesture list.
+ *
+ * @see Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS
+ */
+@MainThread
+@SysUISingleton
+public class AccessibilityGestureTargetsObserver extends
+ SecureSettingsContentObserver<AccessibilityGestureTargetsObserver.TargetsChangedListener> {
+
+ /** Listener for accessibility gesture targets changes. */
+ public interface TargetsChangedListener {
+
+ /**
+ * Called when accessibility gesture targets changes.
+ *
+ * @param targets Current content of {@link Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS}
+ */
+ void onAccessibilityGestureTargetsChanged(String targets);
+ }
+
+ @Inject
+ public AccessibilityGestureTargetsObserver(Context context, UserTracker userTracker) {
+ super(context, userTracker, Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
+ }
+
+ @Override
+ void onValueChanged(TargetsChangedListener listener, String value) {
+ listener.onAccessibilityGestureTargetsChanged(value);
+ }
+
+ /** Returns the current string from settings key
+ * {@link Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS}. */
+ @Nullable
+ public String getCurrentAccessibilityGestureTargets() {
+ return getSettingsValue();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
index abdc3338b7f2..04595a2a698e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.time.DateFormatUtil;
@@ -127,6 +128,9 @@ public class AmbientStatusBarViewController extends ViewController<AmbientStatus
private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback =
this::onStatusBarItemsChanged;
+ private final StatusBarWindowStateListener mStatusBarWindowStateListener =
+ this::onSystemStatusBarStateChanged;
+
@Inject
public AmbientStatusBarViewController(
AmbientStatusBarView view,
@@ -161,10 +165,22 @@ public class AmbientStatusBarViewController extends ViewController<AmbientStatus
mWifiInteractor = wifiInteractor;
mCommunalSceneInteractor = communalSceneInteractor;
mLogger = new DreamLogger(logBuffer, TAG);
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
- statusBarWindowStateController.addListener(this::onSystemStatusBarStateChanged);
+ mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+ }
+
+ @Override
+ public void destroy() {
+ mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
+
+ super.destroy();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index 190bc1587525..d27e72a9c185 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -122,4 +122,9 @@ public interface TouchHandler {
* @param session
*/
void onSessionStart(TouchSession session);
+
+ /**
+ * Called when the handler is being torn down.
+ */
+ default void onDestroy() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index efa55e90081e..1be6f9e7ca4f 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -581,6 +581,10 @@ public class TouchMonitor {
mBoundsFlow.cancel(new CancellationException());
}
+ for (TouchHandler handler : mHandlers) {
+ handler.onDestroy();
+ }
+
mInitialized = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index 8b2449aa1ffd..a8f7fc345001 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -29,6 +29,7 @@ import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
@@ -271,7 +272,7 @@ constructor(
val intent =
Intent(ACTION_BLUETOOTH_DEVICE_DETAILS).apply {
putExtra(
- ":settings:show_fragment_args",
+ EXTRA_SHOW_FRAGMENT_ARGUMENTS,
Bundle().apply {
putString("device_address", deviceItem.cachedBluetoothDevice.address)
}
@@ -292,7 +293,16 @@ constructor(
override fun onAudioSharingButtonClicked(view: View) {
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED)
- startSettingsActivity(Intent(ACTION_AUDIO_SHARING), view)
+ val intent =
+ Intent(ACTION_AUDIO_SHARING).apply {
+ putExtra(
+ EXTRA_SHOW_FRAGMENT_ARGUMENTS,
+ Bundle().apply {
+ putBoolean(LocalBluetoothLeBroadcast.EXTRA_START_LE_AUDIO_SHARING, true)
+ }
+ )
+ }
+ startSettingsActivity(intent, view)
}
private fun cancelJob() {
@@ -320,6 +330,7 @@ constructor(
companion object {
private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog"
private const val CONTENT_HEIGHT_PREF_KEY = Prefs.Key.BLUETOOTH_TILE_DIALOG_CONTENT_HEIGHT
+ private const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
private fun getSubtitleResId(isBluetoothEnabled: Boolean) =
if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index 85fffbf610fc..d125c36cd8db 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -33,9 +33,7 @@ import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
@@ -75,8 +73,6 @@ constructor(
primaryBouncerInteractor: PrimaryBouncerInteractor,
@Application private val applicationScope: CoroutineScope,
private val facePropertyRepository: FacePropertyRepository,
- private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
- faceAuthRepository: DeviceEntryFaceAuthRepository,
private val securityModel: KeyguardSecurityModel,
deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
) {
@@ -97,6 +93,17 @@ constructor(
private val kumCallback =
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+ // Only show the biometric failure messages if the biometric is NOT locked out.
+ // If the biometric is locked out, rely on the lock out message to show
+ // the lockout message & don't override it with the failure message.
+ if (
+ (biometricSourceType == BiometricSourceType.FACE &&
+ deviceEntryBiometricsAllowedInteractor.isFaceLockedOut.value) ||
+ (biometricSourceType == BiometricSourceType.FINGERPRINT &&
+ deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut.value)
+ ) {
+ return
+ }
repository.setMessage(
when (biometricSourceType) {
BiometricSourceType.FINGERPRINT ->
@@ -159,8 +166,8 @@ constructor(
biometricSettingsRepository.authenticationFlags,
trustRepository.isCurrentUserTrustManaged,
isAnyBiometricsEnabledAndEnrolled,
- deviceEntryFingerprintAuthInteractor.isLockedOut,
- faceAuthRepository.isLockedOut,
+ deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut,
+ deviceEntryBiometricsAllowedInteractor.isFaceLockedOut,
isFingerprintAuthCurrentlyAllowedOnBouncer,
::Septuple
)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index df50e8fdb90b..abca518745d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -21,6 +21,7 @@ import com.android.app.tracing.coroutines.flow.collectLatest
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
@@ -39,7 +40,7 @@ sealed class AuthMethodBouncerViewModel(
* being able to attempt to unlock the device.
*/
val isInputEnabled: StateFlow<Boolean>,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
private val _animateFailure = MutableStateFlow(false)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index cfd4f506f169..d21eccdfb047 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -38,6 +38,7 @@ import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage
import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -79,7 +80,7 @@ constructor(
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
private val flags: ComposeBouncerFlags,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
/**
* A message shown when the user has attempted the wrong credential too many times and now must
* wait a while before attempting to authenticate again.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 63b6f0193502..79e5f8d4a683 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -33,6 +33,7 @@ import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.assisted.AssistedFactory
@@ -62,7 +63,7 @@ constructor(
private val pinViewModelFactory: PinBouncerViewModel.Factory,
private val patternViewModelFactory: PatternBouncerViewModel.Factory,
private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index a43447f7fcf4..aae21b97b163 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -66,7 +66,8 @@ public class EditTextActivity extends Activity
@Override
public WindowInsets onApplyWindowInsets(@NonNull View view,
@NonNull WindowInsets windowInsets) {
- Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars());
+ Insets insets = windowInsets.getInsets(
+ WindowInsets.Type.systemBars() | WindowInsets.Type.ime());
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.leftMargin = insets.left;
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
index 46db34618c70..208adc22a3e0 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
@@ -18,6 +18,7 @@ package com.android.systemui.common.data.repository
import android.content.pm.PackageInstaller
import android.os.Handler
+import android.text.TextUtils
import com.android.internal.annotations.GuardedBy
import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.dagger.SysUISingleton
@@ -63,6 +64,7 @@ constructor(
synchronized(sessions) {
sessions.putAll(
packageInstaller.allSessions
+ .filter { !TextUtils.isEmpty(it.appPackageName) }
.map { session -> session.toModel() }
.associateBy { it.sessionId }
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index dff63527ba05..8f1854f93fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -26,7 +26,7 @@ import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.res.R
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 2)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 3)
abstract class CommunalDatabase : RoomDatabase() {
abstract fun communalWidgetDao(): CommunalWidgetDao
@@ -55,7 +55,7 @@ abstract class CommunalDatabase : RoomDatabase() {
context.resources.getString(R.string.config_communalDatabase)
)
.also { builder ->
- builder.addMigrations(MIGRATION_1_2)
+ builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
builder.fallbackToDestructiveMigration(dropAllTables = true)
callback?.let { callback -> builder.addCallback(callback) }
}
@@ -87,5 +87,21 @@ abstract class CommunalDatabase : RoomDatabase() {
)
}
}
+
+ /**
+ * This migration reverses the ranks. For example, if the ranks are 2, 1, 0, then after the
+ * migration they will be 0, 1, 2.
+ */
+ @VisibleForTesting
+ val MIGRATION_2_3 =
+ object : Migration(2, 3) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ Log.i(TAG, "Migrating from version 2 to 3")
+ db.execSQL(
+ "UPDATE communal_item_rank_table " +
+ "SET rank = (SELECT MAX(rank) FROM communal_item_rank_table) - rank"
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 933a25a21578..93b86bd10133 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -97,7 +97,7 @@ constructor(
.addWidget(
widgetId = id,
componentName = name,
- priority = defaultWidgets.size - index,
+ rank = index,
userSerialNumber = userSerialNumber,
)
}
@@ -132,10 +132,17 @@ interface CommunalWidgetDao {
@Query(
"SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
"ON communal_item_rank_table.uid = communal_widget_table.item_id " +
- "ORDER BY communal_item_rank_table.rank DESC"
+ "ORDER BY communal_item_rank_table.rank ASC"
)
fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
+ @Query(
+ "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
+ "ON communal_item_rank_table.uid = communal_widget_table.item_id " +
+ "ORDER BY communal_item_rank_table.rank ASC"
+ )
+ fun getWidgetsNow(): Map<CommunalItemRank, CommunalWidgetItem>
+
@Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
@@ -167,11 +174,11 @@ interface CommunalWidgetDao {
@Query("DELETE FROM communal_item_rank_table") fun clearCommunalItemRankTable()
@Transaction
- fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
- widgetIdToPriorityMap.forEach { (id, priority) ->
+ fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
+ widgetIdToRankMap.forEach { (id, rank) ->
val widget = getWidgetByIdNow(id)
if (widget != null) {
- updateItemRank(widget.itemId, priority)
+ updateItemRank(widget.itemId, rank)
}
}
}
@@ -180,13 +187,13 @@ interface CommunalWidgetDao {
fun addWidget(
widgetId: Int,
provider: ComponentName,
- priority: Int,
+ rank: Int? = null,
userSerialNumber: Int,
): Long {
return addWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
}
@@ -195,13 +202,27 @@ interface CommunalWidgetDao {
fun addWidget(
widgetId: Int,
componentName: String,
- priority: Int,
+ rank: Int? = null,
userSerialNumber: Int,
): Long {
+ val widgets = getWidgetsNow()
+
+ // If rank is not specified, rank it last by finding the current maximum rank and increment
+ // by 1. If the new widget is the first widget, set the rank to 0.
+ val newRank = rank ?: widgets.keys.maxOfOrNull { it.rank + 1 } ?: 0
+
+ // Shift widgets after [rank], unless widget is added at the end.
+ if (rank != null) {
+ widgets.forEach { (rankEntry, widgetEntry) ->
+ if (rankEntry.rank < newRank) return@forEach
+ updateItemRank(widgetEntry.itemId, rankEntry.rank + 1)
+ }
+ }
+
return insertWidget(
widgetId = widgetId,
componentName = componentName,
- itemId = insertItemRank(priority),
+ itemId = insertItemRank(newRank),
userSerialNumber = userSerialNumber,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ad0bfc76ebbf..6cdd9fffe077 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -57,12 +57,17 @@ interface CommunalWidgetRepository {
/** A flow of information about active communal widgets stored in database. */
val communalWidgets: Flow<List<CommunalWidgetContentModel>>
- /** Add a widget at the specified position in the app widget service and the database. */
+ /**
+ * Add a widget in the app widget service and the database.
+ *
+ * @param rank The rank of the widget determines its position in the grid. 0 is first place, 1
+ * is second, etc. If rank is not specified, widget is added at the end.
+ */
fun addWidget(
provider: ComponentName,
user: UserHandle,
- priority: Int,
- configurator: WidgetConfigurator? = null
+ rank: Int?,
+ configurator: WidgetConfigurator? = null,
) {}
/**
@@ -75,9 +80,9 @@ interface CommunalWidgetRepository {
/**
* Update the order of widgets in the database.
*
- * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+ * @param widgetIdToRankMap mapping of the widget ids to the rank of the widget.
*/
- fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
+ fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {}
/**
* Restores the database by reading a state file from disk and updating the widget ids according
@@ -121,7 +126,7 @@ constructor(
CommunalWidgetEntry(
appWidgetId = widget.widgetId,
componentName = widget.componentName,
- priority = rank.rank,
+ rank = rank.rank,
providerInfo = providers[widget.widgetId]
)
}
@@ -151,8 +156,8 @@ constructor(
override fun addWidget(
provider: ComponentName,
user: UserHandle,
- priority: Int,
- configurator: WidgetConfigurator?
+ rank: Int?,
+ configurator: WidgetConfigurator?,
) {
bgScope.launch {
val id = communalWidgetHost.allocateIdAndBindWidget(provider, user)
@@ -190,14 +195,14 @@ constructor(
communalWidgetDao.addWidget(
widgetId = id,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userManager.getUserSerialNumber(user.identifier),
)
backupManager.dataChanged()
} else {
appWidgetHost.deleteAppWidgetId(id)
}
- logger.i("Added widget ${provider.flattenToString()} at position $priority.")
+ logger.i("Added widget ${provider.flattenToString()} at position $rank.")
}
}
@@ -211,11 +216,11 @@ constructor(
}
}
- override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+ override fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
bgScope.launch {
- communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
+ communalWidgetDao.updateWidgetOrder(widgetIdToRankMap)
logger.i({ "Updated the order of widget list with ids: $str1." }) {
- str1 = widgetIdToPriorityMap.toString()
+ str1 = widgetIdToRankMap.toString()
}
backupManager.dataChanged()
}
@@ -342,7 +347,7 @@ constructor(
addWidget(
provider = ComponentName.unflattenFromString(widget.componentName)!!,
user = newUser,
- priority = widget.rank,
+ rank = widget.rank,
)
}
@@ -377,7 +382,7 @@ constructor(
return CommunalWidgetContentModel.Available(
appWidgetId = entry.appWidgetId,
providerInfo = entry.providerInfo!!,
- priority = entry.priority,
+ rank = entry.rank,
)
}
@@ -394,7 +399,7 @@ constructor(
return CommunalWidgetContentModel.Available(
appWidgetId = entry.appWidgetId,
providerInfo = entry.providerInfo!!,
- priority = entry.priority,
+ rank = entry.rank,
)
}
@@ -403,7 +408,7 @@ constructor(
return if (componentName != null && session != null) {
CommunalWidgetContentModel.Pending(
appWidgetId = entry.appWidgetId,
- priority = entry.priority,
+ rank = entry.rank,
componentName = componentName,
icon = session.icon,
user = session.user,
@@ -416,7 +421,7 @@ constructor(
private data class CommunalWidgetEntry(
val appWidgetId: Int,
val componentName: String,
- val priority: Int,
+ val rank: Int,
var providerInfo: AppWidgetProviderInfo? = null,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 7181b15138b9..98abbebd1951 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -140,6 +140,10 @@ constructor(
*/
val editActivityShowing: StateFlow<Boolean> = _editActivityShowing.asStateFlow()
+ private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null)
+
+ val selectedKey: StateFlow<String?> = _selectedKey.asStateFlow()
+
/** Whether communal features are enabled. */
val isCommunalEnabled: StateFlow<Boolean> = communalSettingsInteractor.isCommunalEnabled
@@ -179,6 +183,10 @@ constructor(
}
}
+ fun setSelectedKey(key: String?) {
+ _selectedKey.value = key
+ }
+
/** Whether to show communal when exiting the occluded state. */
val showCommunalFromOccluded: Flow<Boolean> =
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -345,11 +353,10 @@ constructor(
/** Show the widget editor Activity. */
fun showWidgetEditor(
- preselectedKey: String? = null,
shouldOpenWidgetPickerOnStart: Boolean = false,
) {
communalSceneInteractor.setEditModeState(EditModeState.STARTING)
- editWidgetsActivityStarter.startActivity(preselectedKey, shouldOpenWidgetPickerOnStart)
+ editWidgetsActivityStarter.startActivity(shouldOpenWidgetPickerOnStart)
}
/**
@@ -367,13 +374,16 @@ constructor(
/** Dismiss the CTA tile from the hub in view mode. */
suspend fun dismissCtaTile() = communalPrefsInteractor.setCtaDismissed()
- /** Add a widget at the specified position. */
+ /**
+ * Add a widget at the specified rank. If rank is not provided, the widget will be added at the
+ * end.
+ */
fun addWidget(
componentName: ComponentName,
user: UserHandle,
- priority: Int,
+ rank: Int? = null,
configurator: WidgetConfigurator?,
- ) = widgetRepository.addWidget(componentName, user, priority, configurator)
+ ) = widgetRepository.addWidget(componentName, user, rank, configurator)
/**
* Delete a widget by id. Called when user deletes a widget from the hub or a widget is
@@ -384,10 +394,10 @@ constructor(
/**
* Reorder the widgets.
*
- * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+ * @param widgetIdToRankMap mapping of the widget ids to their new priorities.
*/
- fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
- widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
+ fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) =
+ widgetRepository.updateWidgetOrder(widgetIdToRankMap)
/** Request to unpause work profile that is currently in quiet mode. */
fun unpauseWorkProfile() {
@@ -440,7 +450,7 @@ constructor(
is CommunalWidgetContentModel.Available -> {
WidgetContent.Widget(
appWidgetId = widget.appWidgetId,
- priority = widget.priority,
+ rank = widget.rank,
providerInfo = widget.providerInfo,
appWidgetHost = appWidgetHost,
inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
@@ -449,7 +459,7 @@ constructor(
is CommunalWidgetContentModel.Pending -> {
WidgetContent.PendingWidget(
appWidgetId = widget.appWidgetId,
- priority = widget.priority,
+ rank = widget.rank,
componentName = widget.componentName,
icon = widget.icon,
)
@@ -604,11 +614,6 @@ constructor(
_firstVisibleItemOffset = firstVisibleItemOffset
}
- fun resetScrollPosition() {
- _firstVisibleItemIndex = 0
- _firstVisibleItemOffset = 0
- }
-
val firstVisibleItemIndex: Int
get() = _firstVisibleItemIndex
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index a0b996675331..8f756a23a9da 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -88,6 +88,7 @@ constructor(
keyguardState: KeyguardState? = null,
) {
applicationScope.launch("$TAG#changeScene") {
+ if (currentScene.value == newScene) return@launch
logger.logSceneChangeRequested(
from = currentScene.value,
to = newScene,
@@ -108,6 +109,7 @@ constructor(
) {
applicationScope.launch("$TAG#snapToScene") {
delay(delayMillis)
+ if (currentScene.value == newScene) return@launch
logger.logSceneChangeRequested(
from = currentScene.value,
to = newScene,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 73c6ce30b202..4c821d482eef 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -47,12 +47,12 @@ sealed interface CommunalContentModel {
sealed interface WidgetContent : CommunalContentModel {
val appWidgetId: Int
- val priority: Int
+ val rank: Int
val componentName: ComponentName
data class Widget(
override val appWidgetId: Int,
- override val priority: Int,
+ override val rank: Int,
val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: CommunalAppWidgetHost,
val inQuietMode: Boolean,
@@ -71,7 +71,7 @@ sealed interface CommunalContentModel {
data class DisabledWidget(
override val appWidgetId: Int,
- override val priority: Int,
+ override val rank: Int,
val providerInfo: AppWidgetProviderInfo
) : WidgetContent {
override val key = KEY.disabledWidget(appWidgetId)
@@ -85,7 +85,7 @@ sealed interface CommunalContentModel {
data class PendingWidget(
override val appWidgetId: Int,
- override val priority: Int,
+ override val rank: Int,
override val componentName: ComponentName,
val icon: Bitmap? = null,
) : WidgetContent {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 9ce8cf72983a..7cfad60b84cd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -31,7 +31,7 @@ constructor(
private val statsLogProxy: StatsLogProxy,
) {
/** Logs an add widget event for metrics. No-op if widget is not loggable. */
- fun logAddWidget(componentName: String, rank: Int) {
+ fun logAddWidget(componentName: String, rank: Int?) {
if (!componentName.isLoggable()) {
return
}
@@ -39,7 +39,7 @@ constructor(
statsLogProxy.writeCommunalHubWidgetEventReported(
SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
componentName,
- rank,
+ rank ?: -1,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 7cddb7226601..63b1a14b3135 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -24,19 +24,19 @@ import android.os.UserHandle
/** Encapsulates data for a communal widget. */
sealed interface CommunalWidgetContentModel {
val appWidgetId: Int
- val priority: Int
+ val rank: Int
/** Widget is ready to display */
data class Available(
override val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
- override val priority: Int,
+ override val rank: Int,
) : CommunalWidgetContentModel
/** Widget is pending installation */
data class Pending(
override val appWidgetId: Int,
- override val priority: Int,
+ override val rank: Int,
val componentName: ComponentName,
val icon: Bitmap?,
val user: UserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index b8221332eadf..0929d3e1bda9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -55,11 +55,8 @@ abstract class BaseCommunalViewModel(
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
- private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null)
-
/** The key of the currently selected item, or null if no item selected. */
- val selectedKey: StateFlow<String?>
- get() = _selectedKey
+ val selectedKey: StateFlow<String?> = communalInteractor.selectedKey
private val _isTouchConsumed: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -153,7 +150,7 @@ abstract class BaseCommunalViewModel(
open fun onAddWidget(
componentName: ComponentName,
user: UserHandle,
- priority: Int,
+ rank: Int? = null,
configurator: WidgetConfigurator? = null,
) {}
@@ -161,23 +158,23 @@ abstract class BaseCommunalViewModel(
open fun onDeleteWidget(
id: Int,
componentName: ComponentName,
- priority: Int,
+ rank: Int,
) {}
/** Called as the UI detects a tap event on the widget. */
open fun onTapWidget(
componentName: ComponentName,
- priority: Int,
+ rank: Int,
) {}
/**
* Called as the UI requests reordering widgets.
*
- * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
- * add a new item in the middle, provide the priorities of existing widgets as if the new item
- * existed, and then, call [onAddWidget] to add the new item at intended order.
+ * @param widgetIdToRankMap mapping of the widget ids to its rank. When re-ordering to add a new
+ * item in the middle, provide the priorities of existing widgets as if the new item existed,
+ * and then, call [onAddWidget] to add the new item at intended order.
*/
- open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
+ open fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) {}
/** Called as the UI requests opening the widget editor with an optional preselected widget. */
open fun onOpenWidgetEditor(
@@ -226,7 +223,7 @@ abstract class BaseCommunalViewModel(
/** Set the key of the currently selected item */
fun setSelectedKey(key: String?) {
- _selectedKey.value = key
+ communalInteractor.setSelectedKey(key)
}
/** Invoked once touches inside the lazy grid are consumed */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 1a86c717b962..16788d15b269 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -125,24 +125,24 @@ constructor(
override fun onAddWidget(
componentName: ComponentName,
user: UserHandle,
- priority: Int,
+ rank: Int?,
configurator: WidgetConfigurator?
) {
- communalInteractor.addWidget(componentName, user, priority, configurator)
- metricsLogger.logAddWidget(componentName.flattenToString(), priority)
+ communalInteractor.addWidget(componentName, user, rank, configurator)
+ metricsLogger.logAddWidget(componentName.flattenToString(), rank)
}
override fun onDeleteWidget(
id: Int,
componentName: ComponentName,
- priority: Int,
+ rank: Int,
) {
communalInteractor.deleteWidget(id)
- metricsLogger.logRemoveWidget(componentName.flattenToString(), priority)
+ metricsLogger.logRemoveWidget(componentName.flattenToString(), rank)
}
- override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
- communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
+ override fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) =
+ communalInteractor.updateWidgetOrder(widgetIdToRankMap)
override fun onReorderWidgetStart() {
// Clear selection status
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 c0a18f29149d..5a39a6272c94 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
@@ -262,7 +262,7 @@ constructor(
shouldOpenWidgetPickerOnStart: Boolean,
) {
persistScrollPosition()
- communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+ communalInteractor.showWidgetEditor(shouldOpenWidgetPickerOnStart)
}
override fun onDismissCtaTile() {
@@ -272,8 +272,8 @@ constructor(
}
}
- override fun onTapWidget(componentName: ComponentName, priority: Int) {
- metricsLogger.logTapWidget(componentName.flattenToString(), priority)
+ override fun onTapWidget(componentName: ComponentName, rank: Int) {
+ metricsLogger.logTapWidget(componentName.flattenToString(), rank)
}
fun onClick() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index b421e5932352..55a24d0f595a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -16,7 +16,10 @@
package com.android.systemui.communal.widgets
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
import android.content.Intent
+import android.content.IntentSender
import android.os.Bundle
import android.os.RemoteException
import android.util.Log
@@ -34,6 +37,7 @@ import androidx.lifecycle.lifecycleScope
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.PlatformTheme
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -64,16 +68,109 @@ constructor(
companion object {
private const val TAG = "EditWidgetsActivity"
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
- const val EXTRA_PRESELECTED_KEY = "preselected_key"
const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
}
+ /**
+ * [ActivityController] handles closing the activity in the case it is backgrounded without
+ * waiting for an activity result
+ */
+ interface ActivityController {
+ /**
+ * Invoked when waiting for an activity result changes, either initiating such wait or
+ * finishing due to the return of a result.
+ */
+ fun onWaitingForResult(waitingForResult: Boolean) {}
+
+ /** Set the visibility of the activity under control. */
+ fun setActivityFullyVisible(fullyVisible: Boolean) {}
+ }
+
+ /**
+ * A nop ActivityController to be use when the communalEditWidgetsActivityFinishFix flag is
+ * false.
+ */
+ class NopActivityController : ActivityController
+
+ /**
+ * A functional ActivityController to be used when the communalEditWidgetsActivityFinishFix flag
+ * is true.
+ */
+ class ActivityControllerImpl(activity: Activity) : ActivityController {
+ companion object {
+ private const val STATE_EXTRA_IS_WAITING_FOR_RESULT = "extra_is_waiting_for_result"
+ }
+
+ private var waitingForResult = false
+ private var activityFullyVisible = false
+
+ init {
+ activity.registerActivityLifecycleCallbacks(
+ object : ActivityLifecycleCallbacks {
+ override fun onActivityCreated(
+ activity: Activity,
+ savedInstanceState: Bundle?
+ ) {
+ waitingForResult =
+ savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT)
+ ?: false
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+ // Nothing to implement.
+ }
+
+ override fun onActivityResumed(activity: Activity) {
+ // Nothing to implement.
+ }
+
+ override fun onActivityPaused(activity: Activity) {
+ // Nothing to implement.
+ }
+
+ override fun onActivityStopped(activity: Activity) {
+ // If we're not backgrounded due to waiting for a result (either widget
+ // selection or configuration), and we are fully visible, then finish the
+ // activity.
+ if (
+ !waitingForResult &&
+ activityFullyVisible &&
+ !activity.isChangingConfigurations
+ ) {
+ activity.finish()
+ }
+ }
+
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+ outState.putBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT, waitingForResult)
+ }
+
+ override fun onActivityDestroyed(activity: Activity) {
+ // Nothing to implement.
+ }
+ }
+ )
+ }
+
+ override fun onWaitingForResult(waitingForResult: Boolean) {
+ this.waitingForResult = waitingForResult
+ }
+
+ override fun setActivityFullyVisible(fullyVisible: Boolean) {
+ activityFullyVisible = fullyVisible
+ }
+ }
+
private val logger = Logger(logBuffer, "EditWidgetsActivity")
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
private var shouldOpenWidgetPickerOnStart = false
+ private val activityController: ActivityController =
+ if (communalEditWidgetsActivityFinishFix()) ActivityControllerImpl(this)
+ else NopActivityController()
+
private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
@@ -89,11 +186,11 @@ constructor(
if (!isPendingWidgetDrag) {
val (componentName, user) = getWidgetExtraFromIntent(intent)
if (componentName != null && user != null) {
+ // Add widget at the end.
communalViewModel.onAddWidget(
componentName,
user,
- 0,
- widgetConfigurator
+ configurator = widgetConfigurator,
)
} else {
run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
@@ -111,20 +208,19 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
listenForTransitionAndChangeScene()
+ activityController.setActivityFullyVisible(false)
communalViewModel.setEditModeOpen(true)
val windowInsetsController = window.decorView.windowInsetsController
windowInsetsController?.hide(WindowInsets.Type.systemBars())
window.setDecorFitsSystemWindows(false)
- val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
shouldOpenWidgetPickerOnStart =
intent.getBooleanExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, false)
- communalViewModel.setSelectedKey(preselectedKey)
-
setContent {
PlatformTheme {
Box(
@@ -159,6 +255,9 @@ constructor(
communalViewModel.currentScene.first { it == CommunalScenes.Blank }
communalViewModel.setEditModeState(EditModeState.SHOWING)
+ // Inform the ActivityController that we are now fully visible.
+ activityController.setActivityFullyVisible(true)
+
// Show the widget picker, if necessary, after the edit activity has animated in.
// Waiting until after the activity has appeared avoids transitions issues.
if (shouldOpenWidgetPickerOnStart) {
@@ -198,7 +297,34 @@ constructor(
}
}
+ override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
+ activityController.onWaitingForResult(true)
+ super.startActivityForResult(intent, requestCode, options)
+ }
+
+ override fun startIntentSenderForResult(
+ intent: IntentSender,
+ requestCode: Int,
+ fillInIntent: Intent?,
+ flagsMask: Int,
+ flagsValues: Int,
+ extraFlags: Int,
+ options: Bundle?
+ ) {
+ activityController.onWaitingForResult(true)
+ super.startIntentSenderForResult(
+ intent,
+ requestCode,
+ fillInIntent,
+ flagsMask,
+ flagsValues,
+ extraFlags,
+ options
+ )
+ }
+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ activityController.onWaitingForResult(false)
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == WidgetConfigurationController.REQUEST_CODE) {
widgetConfigurator.setConfigurationResult(resultCode)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index af87f09d3c89..63121a83c522 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -19,7 +19,6 @@ package com.android.systemui.communal.widgets
import android.content.Context
import android.content.Intent
import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_OPEN_WIDGET_PICKER_ON_START
-import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -27,7 +26,6 @@ import javax.inject.Inject
interface EditWidgetsActivityStarter {
fun startActivity(
- preselectedKey: String? = null,
shouldOpenWidgetPickerOnStart: Boolean = false,
)
}
@@ -39,12 +37,11 @@ constructor(
private val activityStarter: ActivityStarter,
) : EditWidgetsActivityStarter {
- override fun startActivity(preselectedKey: String?, shouldOpenWidgetPickerOnStart: Boolean) {
+ override fun startActivity(shouldOpenWidgetPickerOnStart: Boolean) {
activityStarter.startActivityDismissingKeyguard(
Intent(applicationContext, EditWidgetsActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
.apply {
- preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) }
putExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, shouldOpenWidgetPickerOnStart)
},
/* onlyProvisioned = */ true,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0363a684ec67..411cbd511a22 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -66,6 +66,7 @@ import com.android.systemui.education.dagger.ContextualEducationModule;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
+import com.android.systemui.haptics.msdl.dagger.MSDLModule;
import com.android.systemui.inputmethod.InputMethodModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
@@ -231,6 +232,7 @@ import javax.inject.Named;
MediaProjectionTaskSwitcherModule.class,
MediaRouterModule.class,
MotionToolModule.class,
+ MSDLModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 9460eaf8abca..d288ccee2ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -57,7 +57,10 @@ import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.Scenes.Bouncer
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -159,6 +162,7 @@ constructor(
private val powerInteractor: PowerInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val sceneInteractor: dagger.Lazy<SceneInteractor>,
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -385,7 +389,16 @@ constructor(
biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
"isFaceAuthEnrolledAndEnabled"
),
- Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
+ Pair(
+ if (SceneContainerFlag.isEnabled) {
+ keyguardTransitionInteractor
+ .isInTransitionWhere(toStatePredicate = { it == KeyguardState.UNDEFINED })
+ .isFalse()
+ } else {
+ keyguardRepository.isKeyguardGoingAway.isFalse()
+ },
+ "keyguardNotGoingAway"
+ ),
Pair(
keyguardTransitionInteractor
.isInTransitionWhere(toStatePredicate = KeyguardState::deviceIsAsleepInState)
@@ -397,7 +410,11 @@ constructor(
.isFalse()
.or(
alternateBouncerInteractor.isVisible.or(
- keyguardInteractor.primaryBouncerShowing
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.get().transitionState.map { it.isIdle(Bouncer) }
+ } else {
+ keyguardInteractor.primaryBouncerShowing
+ }
)
),
"secureCameraNotActiveOrAnyBouncerIsShowing"
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt
index 79b176c8af01..7aee12f70f41 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt
@@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -44,18 +45,30 @@ constructor(
biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
facePropertyRepository: FacePropertyRepository,
) {
+ /**
+ * Whether face is locked out due to too many failed face attempts. This currently includes
+ * whether face is not allowed based on other biometric lockouts; however does not include if
+ * face isn't allowed due to other strong or primary authentication requirements.
+ */
+ val isFaceLockedOut: StateFlow<Boolean> = deviceEntryFaceAuthInteractor.isLockedOut
private val isStrongFaceAuth: Flow<Boolean> =
facePropertyRepository.sensorInfo.map { it?.strength == SensorStrength.STRONG }
private val isStrongFaceAuthLockedOut: Flow<Boolean> =
- combine(isStrongFaceAuth, deviceEntryFaceAuthInteractor.isLockedOut) {
- isStrongFaceAuth,
- isFaceAuthLockedOut ->
+ combine(isStrongFaceAuth, isFaceLockedOut) { isStrongFaceAuth, isFaceAuthLockedOut ->
isStrongFaceAuth && isFaceAuthLockedOut
}
/**
+ * Whether fingerprint is locked out due to too many failed fingerprint attempts. This does NOT
+ * include whether fingerprint is not allowed based on other biometric lockouts nor if
+ * fingerprint isn't allowed due to other strong or primary authentication requirements.
+ */
+ val isFingerprintLockedOut: StateFlow<Boolean> =
+ deviceEntryFingerprintAuthInteractor.isLockedOut
+
+ /**
* Whether fingerprint authentication is currently allowed for the user. This is true if the
* user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
* [com.android.systemui.keyguard.shared.model.AuthenticationFlags], not locked out due to too
@@ -64,7 +77,7 @@ constructor(
*/
val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
combine(
- deviceEntryFingerprintAuthInteractor.isLockedOut,
+ isFingerprintLockedOut,
biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed,
isStrongFaceAuthLockedOut,
) { fpLockedOut, fpAllowedBySettings, strongAuthFaceAuthLockedOut ->
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index 969f53f23f5d..5c058fe92ccb 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -54,7 +54,7 @@ constructor(
val authenticationStatus: Flow<FingerprintAuthenticationStatus> =
repository.authenticationStatus
- val isLockedOut: Flow<Boolean> = repository.isLockedOut
+ val isLockedOut: StateFlow<Boolean> = repository.isLockedOut
val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> =
repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>()
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index c536d6b4f6f8..183e0e96e765 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -46,6 +46,9 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
@@ -90,6 +93,7 @@ constructor(
private val powerInteractor: PowerInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val trustManager: TrustManager,
+ private val sceneInteractor: Lazy<SceneInteractor>,
deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
) : DeviceEntryFaceAuthInteractor {
@@ -103,9 +107,7 @@ constructor(
keyguardUpdateMonitor.setFaceAuthInteractor(this)
observeFaceAuthStateUpdates()
faceAuthenticationLogger.interactorStarted()
- primaryBouncerInteractor
- .get()
- .isShowing
+ isBouncerVisible
.whenItFlipsToTrue()
.onEach {
faceAuthenticationLogger.bouncerVisibilityChanged()
@@ -181,19 +183,23 @@ constructor(
// auth so that the switched user can unlock the device with face auth.
userRepository.selectedUser
.pairwise()
- .onEach { (previous, curr) ->
+ .filter { (previous, curr) ->
val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
- if (wasSwitching && !isSwitching) {
- resetLockedOutState(curr.userInfo.id)
- yield()
- runFaceAuth(
- FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
- // Fallback to detection if bouncer is not showing so that we can detect a
- // face and then show the bouncer to the user if face auth can't run
- fallbackToDetect = !primaryBouncerInteractor.get().isBouncerShowing()
- )
- }
+ // User switching was in progress and is complete now.
+ wasSwitching && !isSwitching
+ }
+ .map { (_, curr) -> curr.userInfo.id }
+ .sample(isBouncerVisible, ::Pair)
+ .onEach { (userId, isBouncerCurrentlyVisible) ->
+ resetLockedOutState(userId)
+ yield()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
+ // Fallback to detection if bouncer is not showing so that we can detect a
+ // face and then show the bouncer to the user if face auth can't run
+ fallbackToDetect = !isBouncerCurrentlyVisible
+ )
}
.launchIn(applicationScope)
@@ -210,6 +216,14 @@ constructor(
.launchIn(applicationScope)
}
+ private val isBouncerVisible: Flow<Boolean> by lazy {
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) }
+ } else {
+ primaryBouncerInteractor.get().isShowing
+ }
+ }
+
private suspend fun resetLockedOutState(currentUserId: Int) {
val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId)
repository.setLockedOut(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b45ebd865c55..905174519245 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
/** Controller for dream overlay animations. */
@@ -84,51 +85,62 @@ constructor(
private var mCurrentBlurRadius: Float = 0f
+ private var mLifecycleFlowHandle: DisposableHandle? = null
+
fun init(view: View) {
this.view = view
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- dreamViewModel.dreamOverlayTranslationY.collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int -> setElementsTranslationYAtPosition(px, position) },
- POSITION_TOP or POSITION_BOTTOM
- )
+ mLifecycleFlowHandle =
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ dreamViewModel.dreamOverlayTranslationY.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationYAtPosition(px, position)
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.dreamOverlayTranslationX.collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int -> setElementsTranslationXAtPosition(px, position) },
- POSITION_TOP or POSITION_BOTTOM
- )
+ launch {
+ dreamViewModel.dreamOverlayTranslationX.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationXAtPosition(px, position)
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.dreamOverlayAlpha.collect { alpha ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsAlphaAtPosition(
- alpha = alpha,
- position = position,
- fadingOut = true,
- )
- },
- POSITION_TOP or POSITION_BOTTOM
- )
+ launch {
+ dreamViewModel.dreamOverlayAlpha.collect { alpha ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = position,
+ fadingOut = true,
+ )
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.transitionEnded.collect { _ ->
- mOverlayStateController.setExitAnimationsRunning(false)
+ launch {
+ dreamViewModel.transitionEnded.collect { _ ->
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
}
}
}
- }
+ }
+
+ fun destroy() {
+ mLifecycleFlowHandle?.dispose()
}
/**
@@ -243,10 +255,8 @@ constructor(
return mAnimator as AnimatorSet
}
- /** Starts the dream content and dream overlay exit animations. */
- fun wakeUp() {
+ fun onWakeUp() {
cancelAnimations()
- mOverlayStateController.setExitAnimationsRunning(true)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 76c7d2383751..3dd2561614b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -59,6 +59,7 @@ import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.ViewController;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.DisposableHandle;
import kotlinx.coroutines.flow.FlowKt;
import java.util.Arrays;
@@ -185,6 +186,8 @@ public class DreamOverlayContainerViewController extends
}
};
+ private DisposableHandle mFlowHandle;
+
@Inject
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
@@ -252,6 +255,17 @@ public class DreamOverlayContainerViewController extends
}
@Override
+ public void destroy() {
+ mStateController.removeCallback(mDreamOverlayStateCallback);
+ mStatusBarViewController.destroy();
+ mComplicationHostViewController.destroy();
+ mDreamOverlayAnimationsController.destroy();
+ mLowLightTransitionCoordinator.setLowLightEnterListener(null);
+
+ super.destroy();
+ }
+
+ @Override
protected void onViewAttached() {
mWakingUpFromSwipe = false;
mJitterStartTimeMillis = System.currentTimeMillis();
@@ -263,7 +277,7 @@ public class DreamOverlayContainerViewController extends
emptyRegion.recycle();
if (dreamHandlesBeingObscured()) {
- collectFlow(
+ mFlowHandle = collectFlow(
mView,
FlowKt.distinctUntilChanged(combineFlows(
mKeyguardTransitionInteractor.isFinishedIn(
@@ -295,6 +309,10 @@ public class DreamOverlayContainerViewController extends
@Override
protected void onViewDetached() {
+ if (mFlowHandle != null) {
+ mFlowHandle.dispose();
+ mFlowHandle = null;
+ }
mHandler.removeCallbacksAndMessages(null);
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
@@ -363,16 +381,17 @@ public class DreamOverlayContainerViewController extends
}
/**
- * Handle the dream waking up and run any necessary animations.
+ * Handle the dream waking up.
*/
- public void wakeUp() {
+ public void onWakeUp() {
+ // TODO(b/361872929): clean up this bool as it doesn't do anything anymore
// When swiping causes wakeup, do not run any animations as the dream should exit as soon
// as possible.
if (mWakingUpFromSwipe) {
return;
}
- mDreamOverlayAnimationsController.wakeUp();
+ mDreamOverlayAnimationsController.onWakeUp();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 7a9537ba4227..caf5b01db846 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -60,7 +60,6 @@ import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.communal.shared.log.CommunalUiEvent;
import com.android.systemui.communal.shared.model.CommunalScenes;
-import com.android.systemui.complication.Complication;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
@@ -70,8 +69,12 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -129,17 +132,21 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
*/
private boolean mBouncerShowing = false;
- private final ComplicationComponent mComplicationComponent;
+ private final com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+ mDreamComplicationComponentFactory;
+ private final ComplicationComponent.Factory mComplicationComponentFactory;
+ private final DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
+ private final AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
- private final AmbientTouchComponent mAmbientTouchComponent;
+ private final TouchInsetManager mTouchInsetManager;
+ private final LifecycleOwner mLifecycleOwner;
- private final com.android.systemui.dreams.complication.dagger.ComplicationComponent
- mDreamComplicationComponent;
- private final DreamOverlayComponent mDreamOverlayComponent;
private ComponentName mCurrentBlockedGestureDreamActivityComponent;
+ private final ArrayList<Job> mFlows = new ArrayList<>();
+
/**
* This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
* handling, should be active. It will automatically be paused when the dream overlay is hidden
@@ -285,36 +292,27 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
+ mComplicationComponentFactory = complicationComponentFactory;
+ mDreamComplicationComponentFactory = dreamComplicationComponentFactory;
mDreamOverlayCallbackController = dreamOverlayCallbackController;
mWindowTitle = windowTitle;
mCommunalInteractor = communalInteractor;
mSystemDialogsCloser = systemDialogsCloser;
mGestureInteractor = gestureInteractor;
-
- final ViewModelStore viewModelStore = new ViewModelStore();
- final Complication.Host host =
- () -> mExecutor.execute(DreamOverlayService.this::requestExit);
-
- mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
- viewModelStore, touchInsetManager);
- mDreamComplicationComponent = dreamComplicationComponentFactory.create(
- mComplicationComponent.getVisibilityController(), touchInsetManager);
- mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
- mComplicationComponent.getComplicationHostViewController(), touchInsetManager);
- mAmbientTouchComponent = ambientTouchComponentFactory.create(lifecycleOwner,
- new HashSet<>(Arrays.asList(
- mDreamComplicationComponent.getHideComplicationTouchHandler(),
- mDreamOverlayComponent.getCommunalTouchHandler())));
+ mDreamOverlayComponentFactory = dreamOverlayComponentFactory;
+ mAmbientTouchComponentFactory = ambientTouchComponentFactory;
+ mTouchInsetManager = touchInsetManager;
+ mLifecycleOwner = lifecycleOwner;
mLifecycleRegistry = lifecycleOwner.getRegistry();
mExecutor.execute(() -> setLifecycleStateLocked(Lifecycle.State.CREATED));
- collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
- mIsCommunalAvailableCallback);
- collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
- mCommunalVisibleConsumer);
- collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
- mBouncerShowingConsumer);
+ mFlows.add(collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
+ mIsCommunalAvailableCallback));
+ mFlows.add(collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
+ mCommunalVisibleConsumer));
+ mFlows.add(collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
+ mBouncerShowingConsumer));
}
@NonNull
@@ -339,6 +337,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
public void onDestroy() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
+ for (Job job : mFlows) {
+ job.cancel(new CancellationException());
+ }
+ mFlows.clear();
+
mExecutor.execute(() -> {
setLifecycleStateLocked(Lifecycle.State.DESTROYED);
@@ -353,6 +356,23 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+ final ComplicationComponent complicationComponent = mComplicationComponentFactory.create(
+ mLifecycleOwner,
+ () -> mExecutor.execute(DreamOverlayService.this::requestExit),
+ new ViewModelStore(), mTouchInsetManager);
+ final com.android.systemui.dreams.complication.dagger.ComplicationComponent
+ dreamComplicationComponent = mDreamComplicationComponentFactory.create(
+ complicationComponent.getVisibilityController(), mTouchInsetManager);
+
+ final DreamOverlayComponent dreamOverlayComponent = mDreamOverlayComponentFactory.create(
+ mLifecycleOwner, complicationComponent.getComplicationHostViewController(),
+ mTouchInsetManager);
+ final AmbientTouchComponent ambientTouchComponent = mAmbientTouchComponentFactory.create(
+ mLifecycleOwner,
+ new HashSet<>(Arrays.asList(
+ dreamComplicationComponent.getHideComplicationTouchHandler(),
+ dreamOverlayComponent.getCommunalTouchHandler())));
+
setLifecycleStateLocked(Lifecycle.State.STARTED);
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
@@ -371,8 +391,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
}
mDreamOverlayContainerViewController =
- mDreamOverlayComponent.getDreamOverlayContainerViewController();
- mTouchMonitor = mAmbientTouchComponent.getTouchMonitor();
+ dreamOverlayComponent.getDreamOverlayContainerViewController();
+ mTouchMonitor = ambientTouchComponent.getTouchMonitor();
mTouchMonitor.init();
mStateController.setShouldShowComplications(shouldShowComplications());
@@ -461,7 +481,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
public void onWakeUp() {
if (mDreamOverlayContainerViewController != null) {
mDreamOverlayCallbackController.onWakeUp();
- mDreamOverlayContainerViewController.wakeUp();
+ mDreamOverlayContainerViewController.onWakeUp();
}
}
@@ -541,6 +561,10 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
}
private void removeContainerViewFromParentLocked() {
+ if (mDreamOverlayContainerViewController == null) {
+ return;
+ }
+
View containerView = mDreamOverlayContainerViewController.getContainerView();
if (containerView == null) {
return;
@@ -559,8 +583,13 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
return;
}
+ // This ensures the container view of the current dream is removed before
+ // the controller is potentially reset.
+ removeContainerViewFromParentLocked();
+
if (mStarted && mWindow != null) {
try {
+ mWindow.clearContentView();
mWindowManager.removeView(mWindow.getDecorView());
} catch (IllegalArgumentException e) {
Log.e(TAG, "Error removing decor view when resetting overlay", e);
@@ -571,7 +600,10 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mStateController.setLowLightActive(false);
mStateController.setEntryAnimationsFinished(false);
- mDreamOverlayContainerViewController = null;
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayContainerViewController.destroy();
+ mDreamOverlayContainerViewController = null;
+ }
if (mTouchMonitor != null) {
mTouchMonitor.destroy();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
index befd822e14cd..d547de24beb5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
@@ -43,6 +43,8 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.lang.ref.WeakReference
+import java.util.concurrent.Executor
typealias FragmentInfoCallback = (TaskFragmentInfo) -> Unit
@@ -68,14 +70,18 @@ constructor(
}
private val fragmentToken = Binder()
- private val organizer: TaskFragmentOrganizer =
- object : TaskFragmentOrganizer(executor) {
- override fun onTransactionReady(transaction: TaskFragmentTransaction) {
- handleTransactionReady(transaction)
- }
- }
- .apply { registerOrganizer(true /* isSystemOrganizer */) }
+ class Organizer(val component: WeakReference<TaskFragmentComponent>, executor: Executor) :
+ TaskFragmentOrganizer(executor) {
+ override fun onTransactionReady(transaction: TaskFragmentTransaction) {
+ component.get()?.handleTransactionReady(transaction)
+ }
+ }
+
+ private val organizer: TaskFragmentOrganizer =
+ Organizer(WeakReference(this), executor).apply {
+ registerOrganizer(true /* isSystemOrganizer */)
+ }
private fun handleTransactionReady(transaction: TaskFragmentTransaction) {
val resultT = WindowContainerTransaction()
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index ee7b6f52ac55..5ba780f9c99d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -33,7 +33,11 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
import java.util.Optional;
+import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -49,6 +53,8 @@ public class CommunalTouchHandler implements TouchHandler {
private final ConfigurationInteractor mConfigurationInteractor;
private Boolean mIsEnabled = false;
+ private ArrayList<Job> mFlows = new ArrayList<>();
+
private int mLayoutDirection = LayoutDirection.LTR;
@VisibleForTesting
@@ -70,17 +76,17 @@ public class CommunalTouchHandler implements TouchHandler {
mCommunalInteractor = communalInteractor;
mConfigurationInteractor = configurationInteractor;
- collectFlow(
+ mFlows.add(collectFlow(
mLifecycle,
mCommunalInteractor.isCommunalAvailable(),
mIsCommunalAvailableCallback
- );
+ ));
- collectFlow(
+ mFlows.add(collectFlow(
mLifecycle,
mConfigurationInteractor.getLayoutDirection(),
mLayoutDirectionCallback
- );
+ ));
}
@Override
@@ -140,4 +146,13 @@ public class CommunalTouchHandler implements TouchHandler {
}
});
}
+
+ @Override
+ public void onDestroy() {
+ for (Job job : mFlows) {
+ job.cancel(new CancellationException());
+ }
+ mFlows.clear();
+ TouchHandler.super.onDestroy();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt
new file mode 100644
index 000000000000..8682848eeea1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.model
+
+import java.time.Instant
+
+data class EduDeviceConnectionTime(
+ val keyboardFirstConnectionTime: Instant? = null,
+ val touchpadFirstConnectionTime: Instant? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 4fd79d764cdd..01f838ff1ea7 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -29,6 +29,7 @@ import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import java.time.Instant
import javax.inject.Inject
@@ -53,10 +54,16 @@ interface ContextualEducationRepository {
fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+ fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime>
+
suspend fun updateGestureEduModel(
gestureType: GestureType,
transform: (GestureEduModel) -> GestureEduModel
)
+
+ suspend fun updateEduDeviceConnectionTime(
+ transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+ )
}
/**
@@ -76,6 +83,8 @@ constructor(
const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
const val USAGE_SESSION_START_TIME_SUFFIX = "_USAGE_SESSION_START_TIME"
const val LAST_EDUCATION_TIME_SUFFIX = "_LAST_EDUCATION_TIME"
+ const val KEYBOARD_FIRST_CONNECTION_TIME = "KEYBOARD_FIRST_CONNECTION_TIME"
+ const val TOUCHPAD_FIRST_CONNECTION_TIME = "TOUCHPAD_FIRST_CONNECTION_TIME"
const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
}
@@ -158,6 +167,37 @@ constructor(
}
}
+ override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> =
+ prefData.map { preferences -> getEduDeviceConnectionTime(preferences) }
+
+ override suspend fun updateEduDeviceConnectionTime(
+ transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+ ) {
+ datastore.filterNotNull().first().edit { preferences ->
+ val currentModel = getEduDeviceConnectionTime(preferences)
+ val updatedModel = transform(currentModel)
+ setInstant(
+ preferences,
+ updatedModel.keyboardFirstConnectionTime,
+ getKeyboardFirstConnectionTimeKey()
+ )
+ setInstant(
+ preferences,
+ updatedModel.touchpadFirstConnectionTime,
+ getTouchpadFirstConnectionTimeKey()
+ )
+ }
+ }
+
+ private fun getEduDeviceConnectionTime(preferences: Preferences): EduDeviceConnectionTime {
+ return EduDeviceConnectionTime(
+ keyboardFirstConnectionTime =
+ preferences[getKeyboardFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) },
+ touchpadFirstConnectionTime =
+ preferences[getTouchpadFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) }
+ )
+ }
+
private fun getSignalCountKey(gestureType: GestureType): Preferences.Key<Int> =
intPreferencesKey(gestureType.name + SIGNAL_COUNT_SUFFIX)
@@ -173,6 +213,12 @@ constructor(
private fun getLastEducationTimeKey(gestureType: GestureType): Preferences.Key<Long> =
longPreferencesKey(gestureType.name + LAST_EDUCATION_TIME_SUFFIX)
+ private fun getKeyboardFirstConnectionTimeKey(): Preferences.Key<Long> =
+ longPreferencesKey(KEYBOARD_FIRST_CONNECTION_TIME)
+
+ private fun getTouchpadFirstConnectionTimeKey(): Preferences.Key<Long> =
+ longPreferencesKey(TOUCHPAD_FIRST_CONNECTION_TIME)
+
private fun setInstant(
preferences: MutablePreferences,
instant: Instant?,
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index db5c386a6c65..10be26e1ce0f 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -22,6 +22,7 @@ import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.data.repository.ContextualEducationRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -32,6 +33,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
@@ -67,6 +69,10 @@ constructor(
.flowOn(backgroundDispatcher)
}
+ suspend fun getEduDeviceConnectionTime(): EduDeviceConnectionTime {
+ return repository.readEduDeviceConnectionTime().first()
+ }
+
suspend fun incrementSignalCount(gestureType: GestureType) {
repository.updateGestureEduModel(gestureType) {
it.copy(
@@ -100,4 +106,16 @@ constructor(
it.copy(usageSessionStartTime = clock.instant(), signalCount = 1)
}
}
+
+ suspend fun updateKeyboardFirstConnectionTime() {
+ repository.updateEduDeviceConnectionTime {
+ it.copy(keyboardFirstConnectionTime = clock.instant())
+ }
+ }
+
+ suspend fun updateTouchpadFirstConnectionTime() {
+ repository.updateEduDeviceConnectionTime {
+ it.copy(touchpadFirstConnectionTime = clock.instant())
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index ad3335b7cdeb..e88349b2b664 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -24,6 +24,7 @@ import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
import java.time.Clock
import javax.inject.Inject
import kotlin.time.Duration.Companion.hours
@@ -39,6 +40,7 @@ class KeyboardTouchpadEduInteractor
constructor(
@Background private val backgroundScope: CoroutineScope,
private val contextualEducationInteractor: ContextualEducationInteractor,
+ private val userInputDeviceRepository: UserInputDeviceRepository,
@EduClock private val clock: Clock,
) : CoreStartable {
@@ -61,6 +63,32 @@ constructor(
}
}
}
+
+ backgroundScope.launch {
+ userInputDeviceRepository.isAnyTouchpadConnectedForUser.collect {
+ if (
+ it.isConnected &&
+ contextualEducationInteractor
+ .getEduDeviceConnectionTime()
+ .touchpadFirstConnectionTime == null
+ ) {
+ contextualEducationInteractor.updateTouchpadFirstConnectionTime()
+ }
+ }
+ }
+
+ backgroundScope.launch {
+ userInputDeviceRepository.isAnyKeyboardConnectedForUser.collect {
+ if (
+ it.isConnected &&
+ contextualEducationInteractor
+ .getEduDeviceConnectionTime()
+ .keyboardFirstConnectionTime == null
+ ) {
+ contextualEducationInteractor.updateKeyboardFirstConnectionTime()
+ }
+ }
+ }
}
private fun isEducationNeeded(model: GestureEduModel): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index 567bf70be3e1..ca43871415e6 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -35,6 +35,7 @@ import android.graphics.drawable.Icon
import android.util.Log
import android.util.Size
import androidx.core.content.res.ResourcesCompat
+import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -162,20 +163,21 @@ constructor(
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Bitmap? {
- return try {
- ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
- configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
- decoder.allocator = allocator
+ ): Bitmap? =
+ traceSection("ImageLoader#loadBitmap") {
+ return try {
+ ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
}
- } catch (e: IOException) {
- Log.w(TAG, "Failed to load source $source", e)
- return null
- } catch (e: DecodeException) {
- Log.w(TAG, "Failed to decode source $source", e)
- return null
}
- }
/**
* Loads passed [Source] on a background thread and returns the [Drawable].
@@ -253,28 +255,31 @@ constructor(
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return try {
- loadDrawableSync(
- toImageDecoderSource(source, defaultContext),
- maxWidth,
- maxHeight,
- allocator
- )
- ?:
- // If we have a resource, retry fallback using the "normal" Resource loading system.
- // This will come into effect in cases like trying to load AnimatedVectorDrawable.
- if (source is Res) {
- val context = source.context ?: defaultContext
- ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
- } else {
- null
- }
- } catch (e: NotFoundException) {
- Log.w(TAG, "Couldn't load resource $source", e)
- null
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return try {
+ loadDrawableSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ ?:
+ // If we have a resource, retry fallback using the "normal" Resource loading
+ // system.
+ // This will come into effect in cases like trying to load
+ // AnimatedVectorDrawable.
+ if (source is Res) {
+ val context = source.context ?: defaultContext
+ ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+ } else {
+ null
+ }
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
}
- }
/**
* Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
@@ -297,20 +302,21 @@ constructor(
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return try {
- ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
- configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
- decoder.allocator = allocator
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return try {
+ ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
}
- } catch (e: IOException) {
- Log.w(TAG, "Failed to load source $source", e)
- return null
- } catch (e: DecodeException) {
- Log.w(TAG, "Failed to decode source $source", e)
- return null
}
- }
/** Loads icon drawable while attempting to size restrict the drawable. */
@WorkerThread
@@ -320,55 +326,59 @@ constructor(
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return when (icon.type) {
- Icon.TYPE_URI,
- Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
- val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
- loadDrawableSync(source, maxWidth, maxHeight, allocator)
- }
- Icon.TYPE_RESOURCE -> {
- val resources = resolveResourcesForIcon(context, icon)
- resources?.let {
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return when (icon.type) {
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+ Icon.TYPE_RESOURCE -> {
+ val resources = resolveResourcesForIcon(context, icon)
+ resources?.let {
+ loadDrawableSync(
+ ImageDecoder.createSource(it, icon.resId),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ // Fallback to non-ImageDecoder load if the attempt failed (e.g. the
+ // resource
+ // is a Vector drawable which ImageDecoder doesn't support.)
+ ?: loadIconDrawable(icon, context)
+ }
+ Icon.TYPE_BITMAP -> {
+ BitmapDrawable(context.resources, icon.bitmap)
+ }
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+ }
+ Icon.TYPE_DATA -> {
loadDrawableSync(
- ImageDecoder.createSource(it, icon.resId),
+ ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
maxWidth,
maxHeight,
allocator
)
}
- // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
- // is a Vector drawable which ImageDecoder doesn't support.)
- ?: loadIconDrawable(icon, context)
- }
- Icon.TYPE_BITMAP -> {
- BitmapDrawable(context.resources, icon.bitmap)
- }
- Icon.TYPE_ADAPTIVE_BITMAP -> {
- AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
- }
- Icon.TYPE_DATA -> {
- loadDrawableSync(
- ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
- maxWidth,
- maxHeight,
- allocator
- )
- }
- else -> {
- // We don't recognize this icon, just fallback.
- loadIconDrawable(icon, context)
+ else -> {
+ // We don't recognize this icon, just fallback.
+ loadIconDrawable(icon, context)
+ }
+ }?.let { drawable ->
+ // Icons carry tint which we need to propagate down to a Drawable.
+ tintDrawable(icon, drawable)
+ drawable
}
- }?.let { drawable ->
- // Icons carry tint which we need to propagate down to a Drawable.
- tintDrawable(icon, drawable)
- drawable
}
- }
@WorkerThread
fun loadIconDrawable(icon: Icon, context: Context): Drawable? {
- icon.loadDrawable(context)?.let { return it }
+ icon.loadDrawable(context)?.let {
+ return it
+ }
Log.w(TAG, "Failed to load drawable for $icon")
return null
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt
new file mode 100644
index 000000000000..5ea96b8388bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl.dagger
+
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.google.android.msdl.domain.MSDLPlayer
+import dagger.Module
+import dagger.Provides
+
+@Module
+object MSDLModule {
+ @Provides
+ @SysUISingleton
+ fun provideMSDLPlayer(@Application context: Context): MSDLPlayer =
+ MSDLPlayer.createPlayer(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
index 6bc640d4d1f4..1aa5ee01f22d 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
@@ -20,7 +20,6 @@ import androidx.activity.compose.BackHandler
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -97,7 +96,6 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
val secondaryFixedDim = LocalAndroidColorScheme.current.secondaryFixedDim
val onSecondaryFixed = LocalAndroidColorScheme.current.onSecondaryFixed
val onSecondaryFixedVariant = LocalAndroidColorScheme.current.onSecondaryFixedVariant
- val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
val dynamicProperties =
rememberLottieDynamicProperties(
rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
@@ -106,11 +104,10 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant)
)
val screenColors =
- remember(surfaceContainer, dynamicProperties) {
+ remember(dynamicProperties) {
TutorialScreenConfig.Colors(
background = onSecondaryFixed,
- successBackground = surfaceContainer,
- title = primaryFixedDim,
+ title = secondaryFixedDim,
animationColors = dynamicProperties,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index c50b7dc06265..b27135674fb1 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -24,13 +24,13 @@ import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -46,7 +46,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
@@ -60,6 +59,7 @@ import com.airbnb.lottie.compose.LottieDynamicProperty
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.compose.modifiers.background
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.FINISHED
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.IN_PROGRESS
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NOT_STARTED
@@ -76,19 +76,11 @@ fun ActionTutorialContent(
onDoneButtonClicked: () -> Unit,
config: TutorialScreenConfig
) {
- val animatedColor by
- animateColorAsState(
- targetValue =
- if (actionState == FINISHED) config.colors.successBackground
- else config.colors.background,
- animationSpec = tween(durationMillis = 150, easing = LinearEasing),
- label = "backgroundColor"
- )
Column(
verticalArrangement = Arrangement.Center,
modifier =
Modifier.fillMaxSize()
- .drawBehind { drawRect(animatedColor) }
+ .background(config.colors.background)
.padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
) {
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
index 0406bb9e6fef..55e5f2d79e60 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
@@ -29,7 +29,6 @@ data class TutorialScreenConfig(
data class Colors(
val background: Color,
- val successBackground: Color,
val title: Color,
val animationColors: LottieDynamicProperties
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 871d04693452..fe3a1e447b07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -79,6 +79,7 @@ import com.android.systemui.SystemUIApplication;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor;
@@ -319,6 +320,7 @@ public class KeyguardService extends Service {
private final WindowManagerOcclusionManager mWmOcclusionManager;
private final KeyguardEnabledInteractor mKeyguardEnabledInteractor;
private final KeyguardWakeDirectlyToGoneInteractor mKeyguardWakeDirectlyToGoneInteractor;
+ private final KeyguardDismissInteractor mKeyguardDismissInteractor;
private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() {
@Override
public FoldGracePeriodProvider get() {
@@ -346,7 +348,8 @@ public class KeyguardService extends Service {
KeyguardInteractor keyguardInteractor,
KeyguardEnabledInteractor keyguardEnabledInteractor,
Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy,
- KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor) {
+ KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor,
+ KeyguardDismissInteractor keyguardDismissInteractor) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -375,6 +378,7 @@ public class KeyguardService extends Service {
mWmOcclusionManager = windowManagerOcclusionManager;
mKeyguardEnabledInteractor = keyguardEnabledInteractor;
mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
+ mKeyguardDismissInteractor = keyguardDismissInteractor;
}
@Override
@@ -482,7 +486,11 @@ public class KeyguardService extends Service {
public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
trace("dismiss message=" + message);
checkPermission();
- mKeyguardViewMediator.dismiss(callback, message);
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardDismissInteractor.dismissKeyguardWithCallback(callback);
+ } else {
+ mKeyguardViewMediator.dismiss(callback, message);
+ }
}
@Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index ba533ce3b1bc..362e016cc97c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -72,6 +72,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.google.android.msdl.domain.MSDLPlayer
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -112,6 +113,7 @@ constructor(
private val keyguardViewMediator: KeyguardViewMediator,
private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>,
@Main private val mainDispatcher: CoroutineDispatcher,
+ private val msdlPlayer: MSDLPlayer,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -219,6 +221,7 @@ constructor(
falsingManager,
keyguardViewMediator,
mainDispatcher,
+ msdlPlayer,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
index 443e98762c47..208a17c0a220 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
@@ -9,3 +9,4 @@ chandruis@google.com
jglazier@google.com
mpietal@google.com
tsuji@google.com
+yuandizhou@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index b1589dadb300..e68d79937063 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -39,7 +39,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.buffer
@@ -53,7 +53,7 @@ import kotlinx.coroutines.flow.stateIn
/** Encapsulates state about device entry fingerprint auth mechanism. */
interface DeviceEntryFingerprintAuthRepository {
/** Whether the device entry fingerprint auth is locked out. */
- val isLockedOut: Flow<Boolean>
+ val isLockedOut: StateFlow<Boolean>
/**
* Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
@@ -127,7 +127,7 @@ constructor(
else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
}
- override val isLockedOut: Flow<Boolean> =
+ override val isLockedOut: StateFlow<Boolean> by lazy {
conflatedCallbackFlow {
val sendLockoutUpdate =
fun() {
@@ -151,7 +151,12 @@ constructor(
sendLockoutUpdate()
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ .stateIn(
+ scope,
+ started = Eagerly,
+ initialValue = keyguardUpdateMonitor.isFingerprintLockedOut
+ )
+ }
override val isRunning: Flow<Boolean>
get() =
@@ -309,6 +314,7 @@ constructor(
) {
sendShouldUpdateIndicatorVisibility(true)
}
+
override fun onStrongAuthStateChanged(userId: Int) {
sendShouldUpdateIndicatorVisibility(true)
}
@@ -318,7 +324,7 @@ constructor(
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
.flowOn(mainDispatcher)
- .shareIn(scope, started = SharingStarted.WhileSubscribed(), replay = 1)
+ .shareIn(scope, started = WhileSubscribed(), replay = 1)
companion object {
const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 49e4c707af18..80a0cee4f319 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -34,7 +34,6 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
@@ -155,12 +154,7 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
}
- } else if (
- powerInteractor.detailedWakefulness.value.lastWakeReason ==
- WakeSleepReason.POWER_BUTTON &&
- isCommunalAvailable &&
- dreamManager.canStartDreaming(true)
- ) {
+ } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
// This case handles tapping the power button to transition through
// dream -> off -> hub.
if (!SceneContainerFlag.isEnabled) {
@@ -226,14 +220,7 @@ constructor(
ownerReason = "waking from dozing"
)
}
- } else if (
- powerInteractor.detailedWakefulness.value.lastWakeReason ==
- WakeSleepReason.POWER_BUTTON &&
- isCommunalAvailable &&
- dreamManager.canStartDreaming(true)
- ) {
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
+ } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index 628e912253eb..d7e6bdb8f02c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -16,9 +16,13 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.internal.policy.IKeyguardDismissCallback
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.model.DismissAction
@@ -28,23 +32,30 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/** Encapsulates business logic for requesting the keyguard to dismiss/finish/done. */
@SysUISingleton
class KeyguardDismissInteractor
@Inject
constructor(
- trustRepository: TrustRepository,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
private val keyguardRepository: KeyguardRepository,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val dismissCallbackRegistry: DismissCallbackRegistry,
+ trustRepository: TrustRepository,
alternateBouncerInteractor: AlternateBouncerInteractor,
powerInteractor: PowerInteractor,
- private val selectedUserInteractor: SelectedUserInteractor,
) {
/*
* Updates when a biometric has authenticated the device and is requesting to dismiss
@@ -127,4 +138,29 @@ constructor(
suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
keyguardRepository.setKeyguardDone(keyguardDoneTiming)
}
+
+ /**
+ * Dismiss the keyguard (or show the bouncer) and invoke the provided callback once dismissed.
+ *
+ * TODO(b/358412565): Support dismiss messages.
+ */
+ fun dismissKeyguardWithCallback(
+ callback: IKeyguardDismissCallback?,
+ ) {
+ scope.launch {
+ withContext(mainDispatcher) {
+ if (callback != null) {
+ dismissCallbackRegistry.addCallback(callback)
+ }
+
+ // This will either show the bouncer, or dismiss the keyguard if insecure.
+ // We currently need to request showing the primary bouncer in order to start a
+ // transition to PRIMARY_BOUNCER. Once we refactor that so that starting the
+ // transition is what causes the bouncer to show, we can remove this entire method,
+ // and simply ask KeyguardTransitionInteractor to transition to a bouncer state or
+ // dismiss keyguard.
+ primaryBouncerInteractor.show(true)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 4cab2bb5dba8..a96d7a8f0997 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
import android.graphics.Point
+import android.util.Log
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
@@ -38,6 +39,7 @@ 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.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
@@ -87,6 +89,7 @@ constructor(
sceneInteractorProvider: Provider<SceneInteractor>,
private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
private val fromLockscreenTransitionInteractor: Provider<FromLockscreenTransitionInteractor>,
+ private val fromOccludedTransitionInteractor: Provider<FromOccludedTransitionInteractor>,
sharedNotificationContainerInteractor: Provider<SharedNotificationContainerInteractor>,
@Application applicationScope: CoroutineScope,
) {
@@ -484,7 +487,11 @@ constructor(
/** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
fun dismissKeyguard() {
- fromLockscreenTransitionInteractor.get().dismissKeyguard()
+ when (keyguardTransitionInteractor.transitionState.value.to) {
+ LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+ OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
+ else -> Log.v(TAG, "Keyguard was dismissed, no direct transition call needed")
+ }
}
fun onCameraLaunchDetected(source: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index cd49c6a4d2e0..4a8ada7f1184 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -27,6 +27,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -67,6 +68,7 @@ constructor(
broadcastDispatcher: BroadcastDispatcher,
private val accessibilityManager: AccessibilityManagerWrapper,
private val pulsingGestureListener: PulsingGestureListener,
+ private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
) {
/** Whether the long-press handling feature should be enabled. */
val isLongPressHandlingEnabled: StateFlow<Boolean> =
@@ -129,7 +131,8 @@ constructor(
}
}
- /** Notifies that the user has long-pressed on the lock screen.
+ /**
+ * Notifies that the user has long-pressed on the lock screen.
*
* @param isA11yAction: Whether the action was performed as an a11y action
*/
@@ -174,6 +177,7 @@ constructor(
/** Notifies that the lockscreen has been clicked at position [x], [y]. */
fun onClick(x: Float, y: Float) {
pulsingGestureListener.onSingleTapUp(x, y)
+ faceAuthInteractor.onNotificationPanelClicked()
}
/** Notifies that the lockscreen has been double clicked. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 89851dbec7bc..a7a832148130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -22,6 +22,7 @@ import android.annotation.DrawableRes
import android.annotation.SuppressLint
import android.graphics.Point
import android.graphics.Rect
+import android.os.VibrationAttributes
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.View
@@ -40,6 +41,7 @@ import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.systemui.Flags.msdlFeedback
import com.android.systemui.Flags.newAodTransition
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
@@ -79,6 +81,9 @@ import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.android.msdl.domain.MSDLPlayer
import kotlin.math.min
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
@@ -112,6 +117,7 @@ object KeyguardRootViewBinder {
falsingManager: FalsingManager?,
keyguardViewMediator: KeyguardViewMediator?,
mainImmediateDispatcher: CoroutineDispatcher,
+ msdlPlayer: MSDLPlayer?,
): DisposableHandle {
val disposables = DisposableHandles()
val childViews = mutableMapOf<Int, View>()
@@ -351,21 +357,43 @@ object KeyguardRootViewBinder {
if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
launch {
deviceEntryHapticsInteractor.playSuccessHaptic.collect {
- vibratorHelper.performHapticFeedback(
- view,
- HapticFeedbackConstants.CONFIRM,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
- )
+ if (msdlFeedback()) {
+ val properties =
+ object : InteractionProperties {
+ override val vibrationAttributes: VibrationAttributes =
+ VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_HARDWARE_FEEDBACK
+ )
+ }
+ msdlPlayer?.playToken(MSDLToken.UNLOCK, properties)
+ } else {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ )
+ }
}
}
launch {
deviceEntryHapticsInteractor.playErrorHaptic.collect {
- vibratorHelper.performHapticFeedback(
- view,
- HapticFeedbackConstants.REJECT,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
- )
+ if (msdlFeedback()) {
+ val properties =
+ object : InteractionProperties {
+ override val vibrationAttributes: VibrationAttributes =
+ VibrationAttributes.createForUsage(
+ VibrationAttributes.USAGE_HARDWARE_FEEDBACK
+ )
+ }
+ msdlPlayer?.playToken(MSDLToken.FAILURE, properties)
+ } else {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.REJECT,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 51ce3556ffbd..f581a2e24546 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -79,6 +79,7 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessage
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.clocks.ClockRegistry
@@ -188,6 +189,7 @@ constructor(
init {
coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
disposables += DisposableHandle { coroutineScope.cancel() }
+ clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
if (KeyguardBottomAreaRefactor.isEnabled) {
quickAffordancesCombinedViewModel.enablePreviewMode(
@@ -416,6 +418,7 @@ constructor(
null, // falsing manager not required for preview mode
null, // keyguard view mediator is not required for preview mode
mainDispatcher,
+ null,
)
}
rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 3e3cbd0540a2..7b0b23ffb2ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.graphics.Color
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
@@ -42,7 +41,6 @@ constructor(
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val dismissCallbackRegistry: DismissCallbackRegistry,
alternateBouncerInteractor: Lazy<AlternateBouncerInteractor>,
- private val primaryBouncerInteractor: PrimaryBouncerInteractor,
) {
// When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be:
private val alternateBouncerScrimAlpha = .66f
@@ -70,13 +68,10 @@ constructor(
fun onRemovedFromWindow() {
statusBarKeyguardViewManager.hideAlternateBouncer(false)
- primaryBouncerInteractor.setDismissAction(null, null)
- dismissCallbackRegistry.notifyDismissCancelled()
}
fun onBackRequested() {
statusBarKeyguardViewManager.hideAlternateBouncer(false)
- primaryBouncerInteractor.setDismissAction(null, null)
dismissCallbackRegistry.notifyDismissCancelled()
}
}
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 54964d6ec768..a96869df001a 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
@@ -162,30 +162,26 @@ constructor(
private val alphaOnShadeExpansion: Flow<Float> =
combineTransform(
- keyguardTransitionInteractor.isInTransition(
- edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
- edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
- ),
- keyguardTransitionInteractor.isInTransition(
- edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
- edgeWithoutSceneContainer =
- Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
+ anyOf(
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
+ edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
+ ),
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
+ edgeWithoutSceneContainer =
+ Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
+ ),
+ keyguardTransitionInteractor.isInTransition(
+ Edge.create(from = LOCKSCREEN, to = DREAMING)
+ ),
),
isOnLockscreen,
shadeInteractor.qsExpansion,
shadeInteractor.shadeExpansion,
- ) {
- lockscreenToGoneTransitionRunning,
- primaryBouncerToLockscreenTransitionRunning,
- isOnLockscreen,
- qsExpansion,
- shadeExpansion ->
+ ) { disabledTransitionRunning, isOnLockscreen, qsExpansion, shadeExpansion ->
// Fade out quickly as the shade expands
- if (
- isOnLockscreen &&
- !lockscreenToGoneTransitionRunning &&
- !primaryBouncerToLockscreenTransitionRunning
- ) {
+ if (isOnLockscreen && !disabledTransitionRunning) {
val alpha =
1f -
MathUtils.constrainedMap(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 666c9f8e243b..2b6c3c080b78 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -20,9 +20,11 @@ import android.content.res.Resources
import com.android.compose.animation.scene.ContentKey
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
@@ -57,7 +59,8 @@ constructor(
private val shadeInteractor: ShadeInteractor,
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
private val occlusionInteractor: SceneContainerOcclusionInteractor,
-) : SysUiViewModel() {
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+) : SysUiViewModel, ExclusiveActivatable() {
@VisibleForTesting val clockSize = clockInteractor.clockSize
val isUdfpsVisible: Boolean
@@ -73,6 +76,10 @@ constructor(
/** Whether the content of the scene UI should be shown. */
val isContentVisible: StateFlow<Boolean> = _isContentVisible.asStateFlow()
+ /** @see DeviceEntryInteractor.isBypassEnabled */
+ val isBypassEnabled: StateFlow<Boolean>
+ get() = deviceEntryInteractor.isBypassEnabled
+
override suspend fun onActivated(): Nothing {
coroutineScope {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index 1314e8863c71..6adf3e9894bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -67,7 +67,7 @@ constructor(
var leaveShadeOpen = false
return transitionAnimation.sharedFlow(
- duration = 200.milliseconds,
+ duration = 80.milliseconds,
onStart = {
leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
startAlpha = viewState.alpha()
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt
deleted file mode 100644
index 03476ec264c2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.lifecycle
-
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.launch
-
-/**
- * A base [Activatable] with the following characteristics:
- * 1. **Can be concurrently activated by no more than one owner.** A previous call to [activate]
- * must be canceled before a new call to [activate] can be made. Trying to call [activate] while
- * already active will fail with an error
- * 2. **Can manage child [Activatable]s**. See [addChild] and [removeChild]. Added children
- * automatically track the activation state of the parent such that when the parent is active,
- * the children are active and vice-versa. Children are also retained such that deactivating the
- * parent and reactivating it also cancels and reactivates the children.
- */
-abstract class BaseActivatable : Activatable {
-
- private val _isActive = AtomicBoolean(false)
-
- var isActive: Boolean
- get() = _isActive.get()
- private set(value) {
- _isActive.set(value)
- }
-
- final override suspend fun activate(): Nothing {
- val allowed = _isActive.compareAndSet(false, true)
- check(allowed) { "Cannot activate an already active activatable!" }
-
- coroutineScope {
- try {
- launch { manageChildren() }
- onActivated()
- } finally {
- isActive = false
- }
- }
- }
-
- /**
- * Notifies that the [Activatable] has been activated.
- *
- * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
- * its state fresh and/or perform side-effects.
- *
- * The method suspends and doesn't return until all work required by the object is finished. In
- * most cases, it's expected for the work to remain ongoing forever so this method will forever
- * suspend its caller until the coroutine that called it is canceled.
- *
- * Implementations could follow this pattern:
- * ```kotlin
- * override suspend fun onActivated(): Nothing {
- * coroutineScope {
- * launch { ... }
- * launch { ... }
- * launch { ... }
- * }
- * }
- * ```
- *
- * @see activate
- */
- protected abstract suspend fun onActivated(): Nothing
-
- private val newChildren = Channel<Activatable>(Channel.BUFFERED)
- private val jobByChild: MutableMap<Activatable, Job> by lazy { mutableMapOf() }
-
- private suspend fun manageChildren(): Nothing {
- coroutineScope {
- // Reactivate children that were added during a previous activation:
- jobByChild.keys.forEach { child -> jobByChild[child] = launch { child.activate() } }
-
- // Process requests to add more children:
- newChildren.receiveAsFlow().collect { newChild ->
- removeChildInternal(newChild)
- jobByChild[newChild] = launch { newChild.activate() }
- }
-
- awaitCancellation()
- }
- }
-
- fun addChild(child: Activatable) {
- newChildren.trySend(child)
- }
-
- fun removeChild(child: Activatable) {
- removeChildInternal(child)
- }
-
- private fun removeChildInternal(child: Activatable) {
- jobByChild.remove(child)?.cancel()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt
new file mode 100644
index 000000000000..08373986611f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.lifecycle
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * A base [Activatable] that can only be activated by a single owner (hence "exclusive"). A previous
+ * call to [activate] must be canceled before a new call to [activate] can be made. Trying to call
+ * [activate] while already active will result in a runtime error.
+ */
+abstract class ExclusiveActivatable : Activatable {
+
+ private val _isActive = AtomicBoolean(false)
+
+ protected var isActive: Boolean
+ get() = _isActive.get()
+ private set(value) {
+ _isActive.set(value)
+ }
+
+ final override suspend fun activate(): Nothing {
+ val allowed = _isActive.compareAndSet(false, true)
+ check(allowed) { "Cannot activate an already active ExclusiveActivatable!" }
+
+ try {
+ onActivated()
+ } finally {
+ isActive = false
+ }
+ }
+
+ /**
+ * Notifies that the [Activatable] has been activated.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+ * its state fresh and/or perform side-effects.
+ *
+ * The method suspends and doesn't return until all work required by the object is finished. In
+ * most cases, it's expected for the work to remain ongoing forever so this method will forever
+ * suspend its caller until the coroutine that called it is canceled.
+ *
+ * Implementations could follow this pattern:
+ * ```kotlin
+ * override suspend fun onActivated(): Nothing {
+ * coroutineScope {
+ * launch { ... }
+ * launch { ... }
+ * launch { ... }
+ * awaitCancellation()
+ * }
+ * }
+ * ```
+ *
+ * @see activate
+ */
+ protected abstract suspend fun onActivated(): Nothing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
new file mode 100644
index 000000000000..59ec2af2d697
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.lifecycle
+
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.snapshots.StateFactoryMarker
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Keeps snapshot/Compose [State]s up-to-date.
+ *
+ * ```kotlin
+ * val hydrator = Hydrator()
+ * val state: Int by hydrator.hydratedStateOf(upstreamFlow)
+ *
+ * override suspend fun activate(): Nothing {
+ * hydrator.activate()
+ * }
+ * ```
+ */
+class Hydrator : ExclusiveActivatable() {
+
+ private val children = mutableListOf<Activatable>()
+
+ /**
+ * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
+ *
+ * @param source The upstream [StateFlow] to collect from; values emitted to it will be
+ * automatically set on the returned [State].
+ */
+ @StateFactoryMarker
+ fun <T> hydratedStateOf(
+ source: StateFlow<T>,
+ ): State<T> {
+ return hydratedStateOf(
+ initialValue = source.value,
+ source = source,
+ )
+ }
+
+ /**
+ * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
+ *
+ * @param initialValue The first value to place on the [State]
+ * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
+ * set on the returned [State].
+ */
+ @StateFactoryMarker
+ fun <T> hydratedStateOf(
+ initialValue: T,
+ source: Flow<T>,
+ ): State<T> {
+ check(!isActive) { "Cannot call hydratedStateOf after Hydrator is already active." }
+
+ val mutableState = mutableStateOf(initialValue)
+ children.add(
+ object : ExclusiveActivatable() {
+ override suspend fun onActivated(): Nothing {
+ source.collect { mutableState.value = it }
+ awaitCancellation()
+ }
+ }
+ )
+ return mutableState
+ }
+
+ override suspend fun onActivated() = coroutineScope {
+ children.forEach { child -> launch { child.activate() } }
+ awaitCancellation()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 979eaef30a23..29ffcbd15125 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -18,75 +18,31 @@ package com.android.systemui.lifecycle
import android.view.View
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.snapshots.StateFactoryMarker
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
-/** Base class for all System UI view-models. */
-abstract class SysUiViewModel : BaseActivatable() {
-
- /**
- * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
- *
- * @param source The upstream [StateFlow] to collect from; values emitted to it will be
- * automatically set on the returned [State].
- */
- @StateFactoryMarker
- fun <T> hydratedStateOf(
- source: StateFlow<T>,
- ): State<T> {
- return hydratedStateOf(
- initialValue = source.value,
- source = source,
- )
- }
-
- /**
- * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
- *
- * @param initialValue The first value to place on the [State]
- * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
- * set on the returned [State].
- */
- @StateFactoryMarker
- fun <T> hydratedStateOf(
- initialValue: T,
- source: Flow<T>,
- ): State<T> {
- val mutableState = mutableStateOf(initialValue)
- addChild(
- object : BaseActivatable() {
- override suspend fun onActivated(): Nothing {
- source.collect { mutableState.value = it }
- awaitCancellation()
- }
- }
- )
- return mutableState
- }
-
- override suspend fun onActivated(): Nothing {
- awaitCancellation()
- }
-}
+/** Defines interface for all System UI view-models. */
+interface SysUiViewModel
/**
- * Returns a remembered [SysUiViewModel] of the type [T] that's automatically kept active until this
- * composable leaves the composition.
- *
- * If the [key] changes, the old [SysUiViewModel] is deactivated and a new one will be instantiated,
+ * Returns a remembered [SysUiViewModel] of the type [T]. If the returned instance is also an
+ * [Activatable], it's automatically kept active until this composable leaves the composition; if
+ * the [key] changes, the old [SysUiViewModel] is deactivated and a new one will be instantiated,
* activated, and returned.
*/
@Composable
fun <T : SysUiViewModel> rememberViewModel(
key: Any = Unit,
factory: () -> T,
-): T = rememberActivated(key, factory)
+): T {
+ val instance = remember(key) { factory() }
+ if (instance is Activatable) {
+ LaunchedEffect(instance) { instance.activate() }
+ }
+ return instance
+}
/**
* Invokes [block] in a new coroutine with a new [SysUiViewModel] that is automatically activated
@@ -100,6 +56,8 @@ suspend fun <T : SysUiViewModel> View.viewModel(
): Nothing =
repeatOnWindowLifecycle(minWindowLifecycleState) {
val instance = factory()
- launch { instance.activate() }
+ if (instance is Activatable) {
+ launch { instance.activate() }
+ }
block(instance)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
new file mode 100644
index 000000000000..b0abdb7c128d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for communal touch-handling logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CommunalTouchLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 5cae58a81cf9..ba3c1d216099 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -618,6 +618,16 @@ public class LogModule {
}
/**
+ * Provides a {@link LogBuffer} for communal touch-handling logs.
+ */
+ @Provides
+ @SysUISingleton
+ @CommunalTouchLog
+ public static LogBuffer provideCommunalTouchLogBuffer(LogBufferFactory factory) {
+ return factory.create("CommunalTouchLog", 250);
+ }
+
+ /**
* Provides a {@link TableLogBuffer} for communal-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
index 9c29bab80d14..ed5080d66c33 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
@@ -22,7 +22,7 @@ import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManage
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
-import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -51,9 +51,8 @@ interface MediaDomainModule {
fun providesMediaDataManager(
legacyProvider: Provider<LegacyMediaDataManagerImpl>,
newProvider: Provider<MediaCarouselInteractor>,
- mediaFlags: MediaFlags,
): MediaDataManager {
- return if (mediaFlags.isSceneContainerEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
newProvider.get()
} else {
legacyProvider.get()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 143d66b69f57..24c57bea8bec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -16,9 +16,8 @@
package com.android.systemui.media.controls.domain.pipeline
+import android.annotation.MainThread
import android.annotation.SuppressLint
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
import android.app.Notification
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
@@ -39,7 +38,6 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
-import android.graphics.drawable.Animatable
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -62,8 +60,10 @@ import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Dumpable
+import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -86,7 +86,6 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.tuner.TunerService
@@ -97,8 +96,13 @@ import com.android.systemui.util.concurrency.ThreadFactory
import com.android.systemui.util.time.SystemClock
import java.io.IOException
import java.io.PrintWriter
+import java.util.Collections
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
// URI fields to try loading album art from
private val ART_URIS =
@@ -167,8 +171,11 @@ private fun allowMediaRecommendations(context: Context): Boolean {
class LegacyMediaDataManagerImpl(
private val context: Context,
@Background private val backgroundExecutor: Executor,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
@Main private val uiExecutor: Executor,
@Main private val foregroundExecutor: DelayableExecutor,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Application private val applicationScope: CoroutineScope,
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
dumpManager: DumpManager,
@@ -188,6 +195,7 @@ class LegacyMediaDataManagerImpl(
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
companion object {
@@ -219,7 +227,12 @@ class LegacyMediaDataManagerImpl(
// listeners are listeners that depend on MediaDataManager.
// TODO(b/159539991#comment5): Move internal listeners to separate package.
private val internalListeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
- private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+ private val mediaEntries: MutableMap<String, MediaData> =
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ Collections.synchronizedMap(LinkedHashMap())
+ } else {
+ LinkedHashMap()
+ }
// There should ONLY be at most one Smartspace media recommendation.
var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
@Keep private var smartspaceSession: SmartspaceSession? = null
@@ -245,8 +258,11 @@ class LegacyMediaDataManagerImpl(
constructor(
context: Context,
threadFactory: ThreadFactory,
+ @Background backgroundDispatcher: CoroutineDispatcher,
@Main uiExecutor: Executor,
@Main foregroundExecutor: DelayableExecutor,
+ @Main mainDispatcher: CoroutineDispatcher,
+ @Application applicationScope: CoroutineScope,
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
broadcastDispatcher: BroadcastDispatcher,
@@ -264,13 +280,17 @@ class LegacyMediaDataManagerImpl(
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ mediaDataLoader: dagger.Lazy<MediaDataLoader>,
) : this(
context,
// Loading bitmap for UMO background can take longer time, so it cannot run on the default
// background thread. Use a custom thread for media.
threadFactory.buildExecutorOnNewThread(TAG),
+ backgroundDispatcher,
uiExecutor,
foregroundExecutor,
+ mainDispatcher,
+ applicationScope,
mediaControllerFactory,
broadcastDispatcher,
dumpManager,
@@ -290,6 +310,7 @@ class LegacyMediaDataManagerImpl(
logger,
smartspaceManager,
keyguardUpdateMonitor,
+ mediaDataLoader,
)
private val appChangeReceiver =
@@ -464,16 +485,31 @@ class LegacyMediaDataManagerImpl(
logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
- backgroundExecutor.execute {
- loadMediaDataInBgForResumption(
- userId,
- desc,
- action,
- token,
- appName,
- appIntent,
- packageName
- )
+
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ applicationScope.launch {
+ loadMediaDataForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
+ } else {
+ backgroundExecutor.execute {
+ loadMediaDataInBgForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
}
}
@@ -498,9 +534,90 @@ class LegacyMediaDataManagerImpl(
oldKey: String?,
isNewlyActiveEntry: Boolean = false,
) {
- backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ applicationScope.launch {
+ loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry)
+ }
+ } else {
+ backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+ }
}
+ private suspend fun loadMediaDataWithLoader(
+ key: String,
+ sbn: StatusBarNotification,
+ oldKey: String?,
+ isNewlyActiveEntry: Boolean = false,
+ ) =
+ withContext(backgroundDispatcher) {
+ val lastActive = systemClock.elapsedRealtime()
+ val result = mediaDataLoader.get().loadMediaData(key, sbn)
+ if (result == null) {
+ Log.d(TAG, "No result from loadMediaData")
+ return@withContext
+ }
+
+ val currentEntry = mediaEntries[key]
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+ val resumeAction: Runnable? = currentEntry?.resumeAction
+ val hasCheckedForResume = currentEntry?.hasCheckedForResume == true
+ val active = currentEntry?.active ?: true
+
+ // We need to log the correct media added.
+ if (isNewlyActiveEntry) {
+ logSingleVsMultipleMediaAdded(result.appUid, sbn.packageName, instanceId)
+ logger.logActiveMediaAdded(
+ result.appUid,
+ sbn.packageName,
+ instanceId,
+ result.playbackLocation
+ )
+ } else if (result.playbackLocation != currentEntry?.playbackLocation) {
+ logger.logPlaybackLocationChange(
+ result.appUid,
+ sbn.packageName,
+ instanceId,
+ result.playbackLocation
+ )
+ }
+
+ withContext(mainDispatcher) {
+ onMediaDataLoaded(
+ key,
+ oldKey,
+ MediaData(
+ userId = sbn.normalizedUserId,
+ initialized = true,
+ app = result.appName,
+ appIcon = result.appIcon,
+ artist = result.artist,
+ song = result.song,
+ artwork = result.artworkIcon,
+ actions = result.actionIcons,
+ actionsToShowInCompact = result.actionsToShowInCompact,
+ semanticActions = result.semanticActions,
+ packageName = sbn.packageName,
+ token = result.token,
+ clickIntent = result.clickIntent,
+ device = result.device,
+ active = active,
+ resumeAction = resumeAction,
+ playbackLocation = result.playbackLocation,
+ notificationKey = key,
+ hasCheckedForResume = hasCheckedForResume,
+ isPlaying = result.isPlaying,
+ isClearable = !sbn.isOngoing,
+ lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
+ instanceId = instanceId,
+ appUid = result.appUid,
+ isExplicit = result.isExplicit,
+ )
+ )
+ }
+ }
+
/** Add a listener for changes in this class */
override fun addListener(listener: MediaDataManager.Listener) {
// mediaDataFilter is the current end of the internal pipeline. Register external
@@ -697,6 +814,75 @@ class LegacyMediaDataManagerImpl(
notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
}
+ private suspend fun loadMediaDataForResumption(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ) =
+ withContext(backgroundDispatcher) {
+ val lastActive = systemClock.elapsedRealtime()
+ val currentEntry = mediaEntries[packageName]
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+ val result =
+ mediaDataLoader
+ .get()
+ .loadMediaDataForResumption(
+ userId,
+ desc,
+ resumeAction,
+ currentEntry,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ if (result == null || desc.title.isNullOrBlank()) {
+ Log.d(TAG, "No MediaData result for resumption")
+ mediaEntries.remove(packageName)
+ return@withContext
+ }
+
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ withContext(mainDispatcher) {
+ onMediaDataLoaded(
+ packageName,
+ null,
+ MediaData(
+ userId = userId,
+ initialized = true,
+ app = result.appName,
+ appIcon = null,
+ artist = result.artist,
+ song = result.song,
+ artwork = result.artworkIcon,
+ actions = result.actionIcons,
+ actionsToShowInCompact = result.actionsToShowInCompact,
+ semanticActions = result.semanticActions,
+ packageName = packageName,
+ token = result.token,
+ clickIntent = result.clickIntent,
+ device = result.device,
+ active = false,
+ resumeAction = resumeAction,
+ resumption = true,
+ notificationKey = packageName,
+ hasCheckedForResume = true,
+ lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
+ instanceId = instanceId,
+ appUid = result.appUid,
+ isExplicit = result.isExplicit,
+ resumeProgress = result.resumeProgress,
+ )
+ )
+ }
+ }
+
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun loadMediaDataInBgForResumption(
userId: Int,
desc: MediaDescription,
@@ -780,6 +966,7 @@ class LegacyMediaDataManagerImpl(
}
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
fun loadMediaDataInBg(
key: String,
sbn: StatusBarNotification,
@@ -802,8 +989,7 @@ class LegacyMediaDataManagerImpl(
notif.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
ApplicationInfo::class.java
- )
- ?: getAppInfoFromPackage(sbn.packageName)
+ ) ?: getAppInfoFromPackage(sbn.packageName)
// App name
val appName = getAppName(sbn, appInfo)
@@ -894,7 +1080,7 @@ class LegacyMediaDataManagerImpl(
var actionsToShowCollapsed: List<Int> = emptyList()
val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
if (semanticActions == null) {
- val actions = createActionsFromNotification(sbn)
+ val actions = createActionsFromNotification(context, activityStarter, sbn)
actionIcons = actions.first
actionsToShowCollapsed = actions.second
}
@@ -975,6 +1161,7 @@ class LegacyMediaDataManagerImpl(
}
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
try {
return context.packageManager.getApplicationInfo(packageName, 0)
@@ -984,6 +1171,7 @@ class LegacyMediaDataManagerImpl(
return null
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
if (name != null) {
@@ -997,264 +1185,19 @@ class LegacyMediaDataManagerImpl(
}
}
- /** Generate action buttons based on notification actions */
- private fun createActionsFromNotification(
- sbn: StatusBarNotification
- ): Pair<List<MediaAction>, List<Int>> {
- val notif = sbn.notification
- val actionIcons: MutableList<MediaAction> = ArrayList()
- val actions = notif.actions
- var actionsToShowCollapsed =
- notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
- ?: mutableListOf()
- if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
- Log.e(
- TAG,
- "Too many compact actions for ${sbn.key}," +
- "limiting to first $MAX_COMPACT_ACTIONS"
- )
- actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
- }
-
- if (actions != null) {
- for ((index, action) in actions.withIndex()) {
- if (index == MAX_NOTIFICATION_ACTIONS) {
- Log.w(
- TAG,
- "Too many notification actions for ${sbn.key}," +
- " limiting to first $MAX_NOTIFICATION_ACTIONS"
- )
- break
- }
- if (action.getIcon() == null) {
- if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}")
- actionsToShowCollapsed.remove(index)
- continue
- }
- val runnable =
- if (action.actionIntent != null) {
- Runnable {
- if (action.actionIntent.isActivity) {
- activityStarter.startPendingIntentDismissingKeyguard(
- action.actionIntent
- )
- } else if (action.isAuthenticationRequired()) {
- activityStarter.dismissKeyguardThenExecute(
- {
- var result = sendPendingIntent(action.actionIntent)
- result
- },
- {},
- true
- )
- } else {
- sendPendingIntent(action.actionIntent)
- }
- }
- } else {
- null
- }
- val mediaActionIcon =
- if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
- Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
- } else {
- action.getIcon()
- }
- .setTint(themeText)
- .loadDrawable(context)
- val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
- actionIcons.add(mediaAction)
- }
- }
- return Pair(actionIcons, actionsToShowCollapsed)
- }
-
- /**
- * Generates action button info for this media session based on the PlaybackState
- *
- * @param packageName Package name for the media app
- * @param controller MediaController for the current session
- * @return a Pair consisting of a list of media actions, and a list of ints representing which
- *
- * ```
- * of those actions should be shown in the compact player
- * ```
- */
private fun createActionsFromState(
packageName: String,
controller: MediaController,
user: UserHandle
): MediaButton? {
- val state = controller.playbackState
- if (state == null || !mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
- return null
- }
-
- // First, check for standard actions
- val playOrPause =
- if (isConnectingState(state.state)) {
- // Spinner needs to be animating to render anything. Start it here.
- val drawable =
- context.getDrawable(com.android.internal.R.drawable.progress_small_material)
- (drawable as Animatable).start()
- MediaAction(
- drawable,
- null, // no action to perform when clicked
- context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
- // Specify a rebind id to prevent the spinner from restarting on later binds.
- com.android.internal.R.drawable.progress_small_material
- )
- } else if (isPlayingState(state.state)) {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
- } else {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
- }
- val prevButton =
- getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
- val nextButton =
- getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
-
- // Then, create a way to build any custom actions that will be needed
- val customActions =
- state.customActions
- .asSequence()
- .filterNotNull()
- .map { getCustomAction(state, packageName, controller, it) }
- .iterator()
- fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
-
- // Finally, assign the remaining button slots: play/pause A B C D
- // A = previous, else custom action (if not reserved)
- // B = next, else custom action (if not reserved)
- // C and D are always custom actions
- val reservePrev =
- controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
- ) == true
- val reserveNext =
- controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
- ) == true
-
- val prevOrCustom =
- if (prevButton != null) {
- prevButton
- } else if (!reservePrev) {
- nextCustomAction()
- } else {
- null
- }
-
- val nextOrCustom =
- if (nextButton != null) {
- nextButton
- } else if (!reserveNext) {
- nextCustomAction()
- } else {
- null
- }
-
- return MediaButton(
- playOrPause,
- nextOrCustom,
- prevOrCustom,
- nextCustomAction(),
- nextCustomAction(),
- reserveNext,
- reservePrev
- )
- }
-
- /**
- * Create a [MediaAction] for a given action and media session
- *
- * @param controller MediaController for the session
- * @param stateActions The actions included with the session's [PlaybackState]
- * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
- * ```
- * [PlaybackState.ACTION_PLAY]
- * [PlaybackState.ACTION_PAUSE]
- * [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
- * [PlaybackState.ACTION_SKIP_TO_NEXT]
- * @return
- * ```
- *
- * A [MediaAction] with correct values set, or null if the state doesn't support it
- */
- private fun getStandardAction(
- controller: MediaController,
- stateActions: Long,
- @PlaybackState.Actions action: Long
- ): MediaAction? {
- if (!includesAction(stateActions, action)) {
+ if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
}
-
- return when (action) {
- PlaybackState.ACTION_PLAY -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
- { controller.transportControls.play() },
- context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container)
- )
- }
- PlaybackState.ACTION_PAUSE -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
- { controller.transportControls.pause() },
- context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container)
- )
- }
- PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_prev),
- { controller.transportControls.skipToPrevious() },
- context.getString(R.string.controls_media_button_prev),
- null
- )
- }
- PlaybackState.ACTION_SKIP_TO_NEXT -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_next),
- { controller.transportControls.skipToNext() },
- context.getString(R.string.controls_media_button_next),
- null
- )
- }
- else -> null
- }
- }
-
- /** Check whether the actions from a [PlaybackState] include a specific action */
- private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
- if (
- (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
- (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
- ) {
- return true
- }
- return (stateActions and action != 0L)
- }
-
- /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
- private fun getCustomAction(
- state: PlaybackState,
- packageName: String,
- controller: MediaController,
- customAction: PlaybackState.CustomAction
- ): MediaAction {
- return MediaAction(
- Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
- { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
- customAction.name,
- null
- )
+ return createActionsFromState(context, packageName, controller)
}
/** Load a bitmap from the various Art metadata URIs */
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
for (uri in ART_URIS) {
val uriString = metadata.getString(uri)
@@ -1269,21 +1212,6 @@ class LegacyMediaDataManagerImpl(
return null
}
- private fun sendPendingIntent(intent: PendingIntent): Boolean {
- return try {
- val options = BroadcastOptions.makeBasic()
- options.setInteractive(true)
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
- intent.send(options.toBundle())
- true
- } catch (e: PendingIntent.CanceledException) {
- Log.d(TAG, "Intent canceled", e)
- false
- }
- }
-
/** Returns a bitmap if the user can access the given URI, else null */
private fun loadBitmapFromUriForUser(
uri: Uri,
@@ -1364,6 +1292,7 @@ class LegacyMediaDataManagerImpl(
)
}
+ @MainThread
fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
traceSection("MediaDataManager#onMediaDataLoaded") {
Assert.isMainThread()
@@ -1619,6 +1548,7 @@ class LegacyMediaDataManagerImpl(
* - If resumption is disabled, we only want to show active players
*/
override fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
+
override fun isRecommendationActive() = smartspaceMediaData.isActive
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
new file mode 100644
index 000000000000..70189b79ea58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Icon
+import android.media.session.MediaController
+import android.media.session.PlaybackState
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import androidx.media.utils.MediaConstants
+import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
+import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.util.kotlin.logI
+
+private const val TAG = "MediaActions"
+
+/**
+ * Generates action button info for this media session based on the PlaybackState
+ *
+ * @param packageName Package name for the media app
+ * @param controller MediaController for the current session
+ * @return a Pair consisting of a list of media actions, and a list of ints representing which of
+ * those actions should be shown in the compact player
+ */
+fun createActionsFromState(
+ context: Context,
+ packageName: String,
+ controller: MediaController,
+): MediaButton? {
+ val state = controller.playbackState ?: return null
+ // First, check for standard actions
+ val playOrPause =
+ if (isConnectingState(state.state)) {
+ // Spinner needs to be animating to render anything. Start it here.
+ val drawable =
+ context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ (drawable as Animatable).start()
+ MediaAction(
+ drawable,
+ null, // no action to perform when clicked
+ context.getString(R.string.controls_media_button_connecting),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material
+ )
+ } else if (isPlayingState(state.state)) {
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PAUSE)
+ } else {
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PLAY)
+ }
+ val prevButton =
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+ val nextButton =
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
+
+ // Then, create a way to build any custom actions that will be needed
+ val customActions =
+ state.customActions
+ .asSequence()
+ .filterNotNull()
+ .map { getCustomAction(context, packageName, controller, it) }
+ .iterator()
+ fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+ // Finally, assign the remaining button slots: play/pause A B C D
+ // A = previous, else custom action (if not reserved)
+ // B = next, else custom action (if not reserved)
+ // C and D are always custom actions
+ val reservePrev =
+ controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
+ ) == true
+ val reserveNext =
+ controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
+ ) == true
+
+ val prevOrCustom =
+ if (prevButton != null) {
+ prevButton
+ } else if (!reservePrev) {
+ nextCustomAction()
+ } else {
+ null
+ }
+
+ val nextOrCustom =
+ if (nextButton != null) {
+ nextButton
+ } else if (!reserveNext) {
+ nextCustomAction()
+ } else {
+ null
+ }
+
+ return MediaButton(
+ playOrPause,
+ nextOrCustom,
+ prevOrCustom,
+ nextCustomAction(),
+ nextCustomAction(),
+ reserveNext,
+ reservePrev
+ )
+}
+
+/**
+ * Create a [MediaAction] for a given action and media session
+ *
+ * @param controller MediaController for the session
+ * @param stateActions The actions included with the session's [PlaybackState]
+ * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
+ * [PlaybackState.ACTION_PLAY] [PlaybackState.ACTION_PAUSE]
+ * [PlaybackState.ACTION_SKIP_TO_PREVIOUS] [PlaybackState.ACTION_SKIP_TO_NEXT]
+ * @return A [MediaAction] with correct values set, or null if the state doesn't support it
+ */
+private fun getStandardAction(
+ context: Context,
+ controller: MediaController,
+ stateActions: Long,
+ @PlaybackState.Actions action: Long
+): MediaAction? {
+ if (!includesAction(stateActions, action)) {
+ return null
+ }
+
+ return when (action) {
+ PlaybackState.ACTION_PLAY -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_play),
+ { controller.transportControls.play() },
+ context.getString(R.string.controls_media_button_play),
+ context.getDrawable(R.drawable.ic_media_play_container)
+ )
+ }
+ PlaybackState.ACTION_PAUSE -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_pause),
+ { controller.transportControls.pause() },
+ context.getString(R.string.controls_media_button_pause),
+ context.getDrawable(R.drawable.ic_media_pause_container)
+ )
+ }
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_prev),
+ { controller.transportControls.skipToPrevious() },
+ context.getString(R.string.controls_media_button_prev),
+ null
+ )
+ }
+ PlaybackState.ACTION_SKIP_TO_NEXT -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_next),
+ { controller.transportControls.skipToNext() },
+ context.getString(R.string.controls_media_button_next),
+ null
+ )
+ }
+ else -> null
+ }
+}
+
+/** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
+private fun getCustomAction(
+ context: Context,
+ packageName: String,
+ controller: MediaController,
+ customAction: PlaybackState.CustomAction
+): MediaAction {
+ return MediaAction(
+ Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
+ { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
+ customAction.name,
+ null
+ )
+}
+
+/** Check whether the actions from a [PlaybackState] include a specific action */
+private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
+ if (
+ (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
+ (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
+ ) {
+ return true
+ }
+ return (stateActions and action != 0L)
+}
+
+/** Generate action buttons based on notification actions */
+fun createActionsFromNotification(
+ context: Context,
+ activityStarter: ActivityStarter,
+ sbn: StatusBarNotification
+): Pair<List<MediaAction>, List<Int>> {
+ val notif = sbn.notification
+ val actionIcons: MutableList<MediaAction> = ArrayList()
+ val actions = notif.actions
+ var actionsToShowCollapsed =
+ notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
+ ?: mutableListOf()
+ if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
+ Log.e(
+ TAG,
+ "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS"
+ )
+ actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
+ }
+
+ actions?.let {
+ if (it.size > MAX_NOTIFICATION_ACTIONS) {
+ Log.w(
+ TAG,
+ "Too many notification actions for ${sbn.key}, " +
+ "limiting to first $MAX_NOTIFICATION_ACTIONS"
+ )
+ }
+
+ for ((index, action) in it.take(MAX_NOTIFICATION_ACTIONS).withIndex()) {
+ if (action.getIcon() == null) {
+ logI(TAG) { "No icon for action $index ${action.title}" }
+ actionsToShowCollapsed.remove(index)
+ continue
+ }
+
+ val runnable =
+ action.actionIntent?.let { actionIntent ->
+ Runnable {
+ when {
+ actionIntent.isActivity ->
+ activityStarter.startPendingIntentDismissingKeyguard(
+ action.actionIntent
+ )
+ action.isAuthenticationRequired ->
+ activityStarter.dismissKeyguardThenExecute(
+ { sendPendingIntent(action.actionIntent) },
+ {},
+ true
+ )
+ else -> sendPendingIntent(actionIntent)
+ }
+ }
+ }
+
+ val themeText =
+ com.android.settingslib.Utils.getColorAttr(
+ context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ .defaultColor
+
+ val mediaActionIcon =
+ when (action.getIcon().type) {
+ Icon.TYPE_RESOURCE ->
+ Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
+ else -> action.getIcon()
+ }
+ .setTint(themeText)
+ .loadDrawable(context)
+
+ val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
+ actionIcons.add(mediaAction)
+ }
+ }
+ return Pair(actionIcons, actionsToShowCollapsed)
+}
+
+private fun sendPendingIntent(intent: PendingIntent): Boolean {
+ return try {
+ intent.send(
+ BroadcastOptions.makeBasic()
+ .apply {
+ setInteractive(true)
+ setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ }
+ .toBundle()
+ )
+ true
+ } catch (e: PendingIntent.CanceledException) {
+ Log.d(TAG, "Intent canceled", e)
+ false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
new file mode 100644
index 000000000000..f9fef8eac815
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.annotation.WorkerThread
+import android.app.Notification
+import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
+import android.app.PendingIntent
+import android.app.StatusBarManager
+import android.app.UriGrantsManager
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.drawable.Icon
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.net.Uri
+import android.os.Process
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import android.support.v4.media.MediaMetadataCompat
+import android.text.TextUtils
+import android.util.Log
+import android.util.Pair
+import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.traceCoroutine
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.statusbar.notification.row.HybridGroupManager
+import com.android.systemui.util.kotlin.logD
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.ensureActive
+
+/** Loads media information from media style [StatusBarNotification] classes. */
+@SysUISingleton
+class MediaDataLoader
+@Inject
+constructor(
+ @Application val context: Context,
+ @Main val mainDispatcher: CoroutineDispatcher,
+ @Background val backgroundScope: CoroutineScope,
+ private val activityStarter: ActivityStarter,
+ private val mediaControllerFactory: MediaControllerFactory,
+ private val mediaFlags: MediaFlags,
+ private val imageLoader: ImageLoader,
+ private val statusBarManager: StatusBarManager,
+) {
+ private val mediaProcessingJobs = ConcurrentHashMap<JobKey, Job>()
+
+ private val artworkWidth: Int =
+ context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize
+ )
+ private val artworkHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+
+ private val themeText =
+ com.android.settingslib.Utils.getColorAttr(
+ context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ .defaultColor
+
+ /**
+ * Loads media data for a given [StatusBarNotification]. It does the loading on the background
+ * thread.
+ *
+ * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed. The method
+ * suspends until loading has completed or failed.
+ *
+ * If a new [loadMediaData] is issued while existing load is in progress, the existing (old)
+ * load will be cancelled.
+ */
+ suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? {
+ logD(TAG) { "Loading media data for $key..." }
+ val jobKey = JobKey(key, sbn)
+ val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) }
+ loadMediaJob.invokeOnCompletion { mediaProcessingJobs.remove(jobKey) }
+ val existingJob = mediaProcessingJobs.put(jobKey, loadMediaJob)
+ existingJob?.cancel("New processing job incoming.")
+ return loadMediaJob.await()
+ }
+
+ /** Loads media data, should be called from [backgroundScope]. */
+ @WorkerThread
+ private suspend fun loadMediaDataInBackground(
+ key: String,
+ sbn: StatusBarNotification,
+ ): MediaDataLoaderResult? =
+ traceCoroutine("MediaDataLoader#loadMediaData") {
+ val token =
+ sbn.notification.extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token::class.java
+ )
+ if (token == null) {
+ Log.i(TAG, "Token was null, not loading media info")
+ return null
+ }
+ val mediaController = mediaControllerFactory.create(token)
+ val metadata = mediaController.metadata
+ val notification: Notification = sbn.notification
+
+ val appInfo =
+ notification.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ ) ?: getAppInfoFromPackage(sbn.packageName)
+
+ // App name
+ val appName = getAppName(sbn, appInfo)
+
+ // Song name
+ var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+ if (song.isNullOrBlank()) {
+ song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+ }
+ if (song.isNullOrBlank()) {
+ song = HybridGroupManager.resolveTitle(notification)
+ }
+ if (song.isNullOrBlank()) {
+ // For apps that don't include a title, log and add a placeholder
+ song = context.getString(R.string.controls_media_empty_title, appName)
+ try {
+ statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier)
+ } catch (e: RuntimeException) {
+ Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}")
+ }
+ }
+
+ // Don't attempt to load bitmaps if the job was cancelled.
+ coroutineContext.ensureActive()
+
+ // Album art
+ var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
+ if (artworkBitmap == null) {
+ artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
+ }
+ if (artworkBitmap == null) {
+ artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+ }
+ val artworkIcon =
+ if (artworkBitmap == null) {
+ notification.getLargeIcon()
+ } else {
+ Icon.createWithBitmap(artworkBitmap)
+ }
+
+ // Don't continue if we were cancelled during slow bitmap load.
+ coroutineContext.ensureActive()
+
+ // App Icon
+ val smallIcon = sbn.notification.smallIcon
+
+ // Explicit Indicator
+ val isExplicit =
+ MediaMetadataCompat.fromMediaMetadata(metadata)
+ ?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+ // Artist name
+ var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
+ if (artist.isNullOrBlank()) {
+ artist = HybridGroupManager.resolveText(notification)
+ }
+
+ // Device name (used for remote cast notifications)
+ val device: MediaDeviceData? = getDeviceInfoForRemoteCast(key, sbn)
+
+ // Control buttons
+ // If flag is enabled and controller has a PlaybackState, create actions from session
+ // info
+ // Otherwise, use the notification actions
+ var actionIcons: List<MediaAction> = emptyList()
+ var actionsToShowCollapsed: List<Int> = emptyList()
+ val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
+ logD(TAG) { "Semantic actions: $semanticActions" }
+ if (semanticActions == null) {
+ val actions = createActionsFromNotification(context, activityStarter, sbn)
+ actionIcons = actions.first
+ actionsToShowCollapsed = actions.second
+ logD(TAG) { "[!!] Semantic actions: $semanticActions" }
+ }
+
+ val playbackLocation = getPlaybackLocation(sbn, mediaController)
+ val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) }
+
+ val appUid = appInfo?.uid ?: Process.INVALID_UID
+ return MediaDataLoaderResult(
+ appName = appName,
+ appIcon = smallIcon,
+ artist = artist,
+ song = song,
+ artworkIcon = artworkIcon,
+ actionIcons = actionIcons,
+ actionsToShowInCompact = actionsToShowCollapsed,
+ semanticActions = semanticActions,
+ token = token,
+ clickIntent = notification.contentIntent,
+ device = device,
+ playbackLocation = playbackLocation,
+ isPlaying = isPlaying,
+ appUid = appUid,
+ isExplicit = isExplicit
+ )
+ }
+
+ /**
+ * Loads media data in background for a given set of resumption parameters. The method suspends
+ * until loading is complete or fails.
+ *
+ * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed.
+ */
+ suspend fun loadMediaDataForResumption(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ currentEntry: MediaData?,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ): MediaDataLoaderResult? {
+ val mediaData =
+ backgroundScope.async {
+ loadMediaDataForResumptionInBackground(
+ userId,
+ desc,
+ resumeAction,
+ currentEntry,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
+ return mediaData.await()
+ }
+
+ /** Loads media data for resumption, should be called from [backgroundScope]. */
+ @WorkerThread
+ private suspend fun loadMediaDataForResumptionInBackground(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ currentEntry: MediaData?,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ): MediaDataLoaderResult? =
+ traceCoroutine("MediaDataLoader#loadMediaDataForResumption") {
+ if (desc.title.isNullOrBlank()) {
+ Log.e(TAG, "Description incomplete")
+ return null
+ }
+
+ logD(TAG) { "adding track for $userId from browser: $desc" }
+
+ val appUid = currentEntry?.appUid ?: Process.INVALID_UID
+
+ // Album art
+ var artworkBitmap = desc.iconBitmap
+ if (artworkBitmap == null && desc.iconUri != null) {
+ artworkBitmap =
+ loadBitmapFromUriForUser(desc.iconUri!!, userId, appUid, packageName)
+ }
+ val artworkIcon =
+ if (artworkBitmap != null) {
+ Icon.createWithBitmap(artworkBitmap)
+ } else {
+ null
+ }
+
+ val isExplicit =
+ desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+ val progress =
+ if (mediaFlags.isResumeProgressEnabled()) {
+ MediaDataUtils.getDescriptionProgress(desc.extras)
+ } else null
+
+ val mediaAction = getResumeMediaAction(resumeAction)
+ return MediaDataLoaderResult(
+ appName = appName,
+ appIcon = null,
+ artist = desc.subtitle,
+ song = desc.title,
+ artworkIcon = artworkIcon,
+ actionIcons = listOf(mediaAction),
+ actionsToShowInCompact = listOf(0),
+ semanticActions = MediaButton(playOrPause = mediaAction),
+ token = token,
+ clickIntent = appIntent,
+ device = null,
+ playbackLocation = 0,
+ isPlaying = null,
+ appUid = appUid,
+ isExplicit = isExplicit,
+ resumeAction = resumeAction,
+ resumeProgress = progress
+ )
+ }
+
+ private fun createActionsFromState(
+ packageName: String,
+ controller: MediaController,
+ user: UserHandle
+ ): MediaButton? {
+ if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+ return null
+ }
+
+ return createActionsFromState(context, packageName, controller)
+ }
+
+ private fun getPlaybackLocation(sbn: StatusBarNotification, mediaController: MediaController) =
+ when {
+ isRemoteCastNotification(sbn) -> MediaData.PLAYBACK_CAST_REMOTE
+ mediaController.playbackInfo?.playbackType ==
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> MediaData.PLAYBACK_LOCAL
+ else -> MediaData.PLAYBACK_CAST_LOCAL
+ }
+
+ /**
+ * Returns [MediaDeviceData] if the [StatusBarNotification] is a remote cast notification.
+ * `null` otherwise.
+ */
+ private fun getDeviceInfoForRemoteCast(
+ key: String,
+ sbn: StatusBarNotification
+ ): MediaDeviceData? {
+ val extras = sbn.notification.extras
+ val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
+ val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
+ val deviceIntent =
+ extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java)
+ logD(TAG) { "$key is RCN for $deviceName" }
+
+ if (deviceName != null && deviceIcon > -1) {
+ // Name and icon must be present, but intent may be null
+ val enabled = deviceIntent != null && deviceIntent.isActivity
+ val deviceDrawable =
+ Icon.createWithResource(sbn.packageName, deviceIcon)
+ .loadDrawable(sbn.getPackageContext(context))
+ return MediaDeviceData(
+ enabled,
+ deviceDrawable,
+ deviceName,
+ deviceIntent,
+ showBroadcastButton = false
+ )
+ }
+ return null
+ }
+
+ private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
+ try {
+ return context.packageManager.getApplicationInfo(packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app info for $packageName", e)
+ return null
+ }
+ }
+
+ private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
+ val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
+ return when {
+ name != null -> name
+ appInfo != null -> context.packageManager.getApplicationLabel(appInfo).toString()
+ else -> sbn.packageName
+ }
+ }
+
+ /** Load a bitmap from the various Art metadata URIs */
+ private suspend fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
+ for (uri in ART_URIS) {
+ val uriString = metadata.getString(uri)
+ if (!TextUtils.isEmpty(uriString)) {
+ val albumArt = loadBitmapFromUri(Uri.parse(uriString))
+ // If we got cancelled during slow album art load, cancel the rest of
+ // the process.
+ coroutineContext.ensureActive()
+ if (albumArt != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "loaded art from $uri")
+ }
+ return albumArt
+ }
+ }
+ }
+ return null
+ }
+
+ private suspend fun loadBitmapFromUri(uri: Uri): Bitmap? {
+ // ImageDecoder requires a scheme of the following types
+ if (
+ uri.scheme !in
+ listOf(
+ ContentResolver.SCHEME_CONTENT,
+ ContentResolver.SCHEME_ANDROID_RESOURCE,
+ ContentResolver.SCHEME_FILE
+ )
+ ) {
+ Log.w(TAG, "Invalid album art uri $uri")
+ return null
+ }
+
+ val source = ImageLoader.Uri(uri)
+ return imageLoader.loadBitmap(
+ source,
+ artworkWidth,
+ artworkHeight,
+ allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ )
+ }
+
+ private suspend fun loadBitmapFromUriForUser(
+ uri: Uri,
+ userId: Int,
+ appUid: Int,
+ packageName: String
+ ): Bitmap? {
+ try {
+ val ugm = UriGrantsManager.getService()
+ ugm.checkGrantUriPermission_ignoreNonSystem(
+ appUid,
+ packageName,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, userId)
+ )
+ return loadBitmapFromUri(uri)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "Failed to get URI permission: $e")
+ }
+ return null
+ }
+
+ /** Check whether this notification is an RCN */
+ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean =
+ sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
+
+ private fun getResumeMediaAction(action: Runnable): MediaAction {
+ return MediaAction(
+ Icon.createWithResource(context, R.drawable.ic_media_play)
+ .setTint(themeText)
+ .loadDrawable(context),
+ action,
+ context.getString(R.string.controls_media_resume),
+ context.getDrawable(R.drawable.ic_media_play_container)
+ )
+ }
+
+ private data class JobKey(val key: String, val sbn: StatusBarNotification) :
+ Pair<String, StatusBarNotification>(key, sbn)
+
+ companion object {
+ private const val TAG = "MediaDataLoader"
+ private val ART_URIS =
+ arrayOf(
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ MediaMetadata.METADATA_KEY_ART_URI,
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ )
+ }
+
+ /** Returned data from loader. */
+ data class MediaDataLoaderResult(
+ val appName: String?,
+ val appIcon: Icon?,
+ val artist: CharSequence?,
+ val song: CharSequence?,
+ val artworkIcon: Icon?,
+ val actionIcons: List<MediaAction>,
+ val actionsToShowInCompact: List<Int>,
+ val semanticActions: MediaButton?,
+ val token: MediaSession.Token?,
+ val clickIntent: PendingIntent?,
+ val device: MediaDeviceData?,
+ val playbackLocation: Int,
+ val isPlaying: Boolean?,
+ val appUid: Int,
+ val isExplicit: Boolean,
+ val resumeAction: Runnable? = null,
+ val resumeProgress: Double? = null
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index adcfba75f498..916f8b2e1730 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -73,6 +73,7 @@ import com.android.systemui.media.controls.data.repository.MediaDataRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaAction
@@ -90,6 +91,7 @@ import com.android.systemui.media.controls.util.SmallHash
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
@@ -271,7 +273,7 @@ class MediaDataProcessor(
}
override fun start() {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
return
}
@@ -1043,14 +1045,13 @@ class MediaDataProcessor(
val playOrPause =
if (isConnectingState(state.state)) {
// Spinner needs to be animating to render anything. Start it here.
- val drawable =
- context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ val drawable = MediaControlDrawables.getProgress(context)
(drawable as Animatable).start()
MediaAction(
drawable,
null, // no action to perform when clicked
context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
+ MediaControlDrawables.getConnecting(context),
// Specify a rebind id to prevent the spinner from restarting on later binds.
com.android.internal.R.drawable.progress_small_material
)
@@ -1143,23 +1144,23 @@ class MediaDataProcessor(
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
+ MediaControlDrawables.getPlayIcon(context),
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container)
+ MediaControlDrawables.getPlayBackground(context)
)
}
PlaybackState.ACTION_PAUSE -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
+ MediaControlDrawables.getPauseIcon(context),
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container)
+ MediaControlDrawables.getPauseBackground(context)
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_prev),
+ MediaControlDrawables.getPrevIcon(context),
{ controller.transportControls.skipToPrevious() },
context.getString(R.string.controls_media_button_prev),
null
@@ -1167,7 +1168,7 @@ class MediaDataProcessor(
}
PlaybackState.ACTION_SKIP_TO_NEXT -> {
MediaAction(
- context.getDrawable(R.drawable.ic_media_next),
+ MediaControlDrawables.getNextIcon(context),
{ controller.transportControls.skipToNext() },
context.getString(R.string.controls_media_button_next),
null
@@ -1308,7 +1309,7 @@ class MediaDataProcessor(
.loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container)
+ MediaControlDrawables.getPlayBackground(context)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index eab0d483c3e5..a193f7f8f498 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -39,6 +39,7 @@ import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.media.flags.Flags
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.util.LocalMediaManagerFactory
@@ -142,6 +143,7 @@ constructor(
interface Listener {
/** Called when the route has changed for a given notification. */
fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
+
/** Called when the notification was removed. */
fun onKeyRemoved(key: String, userInitiated: Boolean)
}
@@ -159,6 +161,7 @@ constructor(
val token
get() = controller?.sessionToken
+
private var started = false
private var playbackType = PLAYBACK_TYPE_UNKNOWN
private var playbackVolumeControlId: String? = null
@@ -170,6 +173,7 @@ constructor(
fgExecutor.execute { processDevice(key, oldKey, value) }
}
}
+
// A device that is not yet connected but is expected to connect imminently. Because it's
// expected to connect imminently, it should be displayed as the current device.
private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
@@ -354,12 +358,12 @@ constructor(
activeDevice =
routingSession?.let {
- val icon = if (it.selectedRoutes.size > 1) {
- context.getDrawable(
- com.android.settingslib.R.drawable.ic_media_group_device)
- } else {
- connectedDevice?.icon // Single route. We don't change the icon.
- }
+ val icon =
+ if (it.selectedRoutes.size > 1) {
+ MediaControlDrawables.getGroupDevice(context)
+ } else {
+ connectedDevice?.icon // Single route. We don't change the icon.
+ }
// For a remote session, always use the current device from
// LocalMediaManager. Override with routing session information if
// available:
@@ -367,14 +371,16 @@ constructor(
// - Icon: To show the group icon if there's more than one selected
// route.
connectedDevice?.copy(
- name = it.name ?: connectedDevice.name,
- icon = icon)
- } ?: MediaDeviceData(
- enabled = false,
- icon = context.getDrawable(R.drawable.ic_media_home_devices),
- name = context.getString(R.string.media_seamless_other_device),
- showBroadcastButton = false
- )
+ name = it.name ?: connectedDevice.name,
+ icon = icon
+ )
+ }
+ ?: MediaDeviceData(
+ enabled = false,
+ icon = MediaControlDrawables.getHomeDevices(context),
+ name = context.getString(R.string.media_seamless_other_device),
+ showBroadcastButton = false
+ )
} else {
// Prefer SASS if available when playback is local.
activeDevice = getSassDevice() ?: connectedDevice
@@ -434,10 +440,7 @@ constructor(
return if (enableLeAudioSharing()) {
MediaDeviceData(
enabled = false,
- icon =
- context.getDrawable(
- com.android.settingslib.R.drawable.ic_bt_le_audio_sharing
- ),
+ icon = MediaControlDrawables.getLeAudioSharing(context),
name = context.getString(R.string.audio_sharing_description),
intent = null,
showBroadcastButton = false
@@ -445,13 +448,14 @@ constructor(
} else {
MediaDeviceData(
enabled = true,
- icon = context.getDrawable(R.drawable.settings_input_antenna),
+ icon = MediaControlDrawables.getAntenna(context),
name = broadcastDescription,
intent = null,
showBroadcastButton = true
)
}
}
+
/** Return a display name for the current device / route, or null if not possible */
private fun getDeviceName(
device: MediaDevice?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index 9d7160cbaffc..270ab72e291d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -105,7 +105,7 @@ constructor(
val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia
override fun start() {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
index 28ee668088c1..c78220e42d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
@@ -45,128 +45,139 @@ object MediaControlDrawables {
private var solid: Drawable? = null
fun getProgress(context: Context): Drawable? {
- return progress
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ }
+ return progress?.mutate()
?: context.getDrawable(com.android.internal.R.drawable.progress_small_material).also {
- if (!mediaControlsDrawablesReuse()) return@also
progress = it
}
}
fun getConnecting(context: Context): Drawable? {
- return connecting
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_connecting_container)
+ }
+ return connecting?.mutate()
?: context.getDrawable(R.drawable.ic_media_connecting_container).also {
- if (!mediaControlsDrawablesReuse()) return@also
connecting = it
}
}
fun getPlayIcon(context: Context): AnimatedVectorDrawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?
+ }
return playIcon?.let {
it.reset()
- it
+ it.mutate() as AnimatedVectorDrawable
}
?: (context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?).also {
- if (!mediaControlsDrawablesReuse()) return@also
playIcon = it
}
}
fun getPlayBackground(context: Context): AnimatedVectorDrawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_play_container)
+ as AnimatedVectorDrawable?
+ }
return playBackground?.let {
it.reset()
- it
+ it.mutate() as AnimatedVectorDrawable
}
?: (context.getDrawable(R.drawable.ic_media_play_container) as AnimatedVectorDrawable?)
- .also {
- if (!mediaControlsDrawablesReuse()) return@also
- playBackground = it
- }
+ .also { playBackground = it }
}
fun getPauseIcon(context: Context): AnimatedVectorDrawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?
+ }
return pauseIcon?.let {
it.reset()
- it
+ it.mutate() as AnimatedVectorDrawable
}
?: (context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?).also {
- if (!mediaControlsDrawablesReuse()) return@also
pauseIcon = it
}
}
fun getPauseBackground(context: Context): AnimatedVectorDrawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_pause_container)
+ as AnimatedVectorDrawable?
+ }
return pauseBackground?.let {
it.reset()
- it
+ it.mutate() as AnimatedVectorDrawable
}
?: (context.getDrawable(R.drawable.ic_media_pause_container) as AnimatedVectorDrawable?)
- .also {
- if (!mediaControlsDrawablesReuse()) return@also
- pauseBackground = it
- }
+ .also { pauseBackground = it }
}
fun getNextIcon(context: Context): Drawable? {
- return nextIcon
- ?: context.getDrawable(R.drawable.ic_media_next).also {
- if (!mediaControlsDrawablesReuse()) return@also
- nextIcon = it
- }
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_next)
+ }
+ return nextIcon ?: context.getDrawable(R.drawable.ic_media_next).also { nextIcon = it }
}
fun getPrevIcon(context: Context): Drawable? {
- return prevIcon
- ?: context.getDrawable(R.drawable.ic_media_prev).also {
- if (!mediaControlsDrawablesReuse()) return@also
- prevIcon = it
- }
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_prev)
+ }
+ return prevIcon ?: context.getDrawable(R.drawable.ic_media_prev).also { prevIcon = it }
}
fun getLeAudioSharing(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
+ }
return leAudioSharing
?: context.getDrawable(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing).also {
- if (!mediaControlsDrawablesReuse()) return@also
leAudioSharing = it
}
}
fun getAntenna(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.settings_input_antenna)
+ }
return antenna
- ?: context.getDrawable(R.drawable.settings_input_antenna).also {
- if (!mediaControlsDrawablesReuse()) return@also
- antenna = it
- }
+ ?: context.getDrawable(R.drawable.settings_input_antenna).also { antenna = it }
}
fun getGroupDevice(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(com.android.settingslib.R.drawable.ic_media_group_device)
+ }
return groupDevice
?: context.getDrawable(com.android.settingslib.R.drawable.ic_media_group_device).also {
- if (!mediaControlsDrawablesReuse()) return@also
groupDevice = it
}
}
fun getHomeDevices(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.ic_media_home_devices)
+ }
return homeDevices
- ?: context.getDrawable(R.drawable.ic_media_home_devices).also {
- if (!mediaControlsDrawablesReuse()) return@also
- homeDevices = it
- }
+ ?: context.getDrawable(R.drawable.ic_media_home_devices).also { homeDevices = it }
}
fun getOutline(context: Context): Drawable? {
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.qs_media_outline_button)
+ }
return outline
- ?: context.getDrawable(R.drawable.qs_media_outline_button).also {
- if (!mediaControlsDrawablesReuse()) return@also
- outline = it
- }
+ ?: context.getDrawable(R.drawable.qs_media_outline_button).also { outline = it }
}
fun getSolid(context: Context): Drawable? {
- return solid
- ?: context.getDrawable(R.drawable.qs_media_solid_button).also {
- if (!mediaControlsDrawablesReuse()) return@also
- solid = it
- }
+ if (!mediaControlsDrawablesReuse()) {
+ return context.getDrawable(R.drawable.qs_media_solid_button)
+ }
+ return solid ?: context.getDrawable(R.drawable.qs_media_solid_button).also { solid = it }
}
}
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 fb2bbde37a18..19cdee7befdd 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
@@ -220,7 +220,7 @@ constructor(
private val animationScaleObserver: ContentObserver =
object : ContentObserver(executor, 0) {
override fun onChange(selfChange: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
} else {
controllerById.values.forEach { it.updateAnimatorDurationScale() }
@@ -350,7 +350,7 @@ constructor(
inflateSettingsButton()
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
configurationController.addCallback(configListener)
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
setUpListeners()
} else {
val visualStabilityCallback = OnReorderingAllowedListener {
@@ -391,7 +391,7 @@ constructor(
listenForAnyStateToGoneKeyguardTransition(this)
listenForAnyStateToLockscreenTransition(this)
- if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle
+ if (!SceneContainerFlag.isEnabled) return@repeatOnLifecycle
listenForMediaItemsChanges(this)
}
}
@@ -733,7 +733,7 @@ constructor(
when (commonViewModel) {
is MediaCommonViewModel.MediaControl -> {
val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
viewController.widthInSceneContainerPx = widthInSceneContainerPx
viewController.heightInSceneContainerPx = heightInSceneContainerPx
}
@@ -965,7 +965,7 @@ constructor(
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
val newPlayer = mediaControlPanelFactory.get()
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
newPlayer.mediaViewController.widthInSceneContainerPx = widthInSceneContainerPx
newPlayer.mediaViewController.heightInSceneContainerPx =
heightInSceneContainerPx
@@ -1140,7 +1140,7 @@ constructor(
}
private fun updatePlayers(recreateMedia: Boolean) {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
updateMediaPlayers(recreateMedia)
return
}
@@ -1240,7 +1240,7 @@ constructor(
currentStartLocation = startLocation
currentEndLocation = endLocation
currentTransitionProgress = progress
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
for (mediaPlayer in MediaPlayerData.players()) {
updateViewControllerToState(mediaPlayer.mediaViewController, immediately)
}
@@ -1300,7 +1300,7 @@ constructor(
/** Update listening to seekbar. */
private fun updateSeekbarListening(visibleToUser: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
for (player in MediaPlayerData.players()) {
player.setListening(visibleToUser && currentlyExpanded)
}
@@ -1313,7 +1313,7 @@ constructor(
private fun updateCarouselDimensions() {
var width = 0
var height = 0
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
for (mediaPlayer in MediaPlayerData.players()) {
val controller = mediaPlayer.mediaViewController
// When transitioning the view to gone, the view gets smaller, but the translation
@@ -1405,7 +1405,7 @@ constructor(
!mediaManager.hasActiveMediaOrRecommendation() &&
desiredHostState.showsOnlyActiveMedia
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
for (mediaPlayer in MediaPlayerData.players()) {
if (animate) {
mediaPlayer.mediaViewController.animatePendingStateChange(
@@ -1445,7 +1445,7 @@ constructor(
}
fun closeGuts(immediate: Boolean = true) {
- if (!mediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
MediaPlayerData.players().forEach { it.closeGuts(immediate) }
} else {
controllerById.values.forEach { it.closeGuts(immediate) }
@@ -1596,7 +1596,7 @@ constructor(
@VisibleForTesting
fun onSwipeToDismiss() {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
mediaCarouselViewModel.onSwipeToDismiss(currentEndLocation)
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index addb0147889f..87610cf774a3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -111,7 +111,6 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder;
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder;
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel;
import com.android.systemui.media.controls.util.MediaDataUtils;
-import com.android.systemui.media.controls.util.MediaFlags;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
import com.android.systemui.media.controls.util.SmallHash;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
@@ -120,6 +119,7 @@ import com.android.systemui.monet.Style;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -209,7 +209,6 @@ public class MediaControlPanel {
static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
private final SeekBarViewModel mSeekBarViewModel;
- private final MediaFlags mMediaFlags;
private final CommunalSceneInteractor mCommunalSceneInteractor;
private SeekBarObserver mSeekBarObserver;
protected final Executor mBackgroundExecutor;
@@ -323,8 +322,7 @@ public class MediaControlPanel {
CommunalSceneInteractor communalSceneInteractor,
NotificationLockscreenUserManager lockscreenUserManager,
BroadcastDialogController broadcastDialogController,
- GlobalSettings globalSettings,
- MediaFlags mediaFlags
+ GlobalSettings globalSettings
) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
@@ -343,7 +341,6 @@ public class MediaControlPanel {
mActivityIntentHelper = activityIntentHelper;
mLockscreenUserManager = lockscreenUserManager;
mBroadcastDialogController = broadcastDialogController;
- mMediaFlags = mediaFlags;
mCommunalSceneInteractor = communalSceneInteractor;
mSeekBarViewModel.setLogSeek(() -> {
@@ -641,7 +638,7 @@ public class MediaControlPanel {
// State refresh interferes with the translation animation, only run it if it's not running.
if (!mMetadataAnimationHandler.isRunning()) {
// Don't refresh in scene framework, because it will calculate with invalid layout sizes
- if (!mMediaFlags.isSceneContainerEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mMediaViewController.refreshState();
}
}
@@ -909,7 +906,7 @@ public class MediaControlPanel {
// Capture width & height from views in foreground for artwork scaling in background
int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
- if (mMediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) {
+ if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) {
// TODO(b/312714128): ensure we have a valid size before setting background
width = mMediaViewController.getWidthInSceneContainerPx();
height = mMediaViewController.getHeightInSceneContainerPx();
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 091b886c7ba4..a9d2a541a241 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -46,10 +46,10 @@ import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.view.MediaHost
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.StatusBarState
@@ -119,7 +119,6 @@ constructor(
@Application private val coroutineScope: CoroutineScope,
private val splitShadeStateController: SplitShadeStateController,
private val logger: MediaViewLogger,
- private val mediaFlags: MediaFlags,
) {
/** Track the media player setting status on lock screen. */
@@ -1111,7 +1110,7 @@ constructor(
private fun updateHostAttachment() =
traceSection("MediaHierarchyManager#updateHostAttachment") {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
// No need to manage transition states - just update the desired location directly
logger.logMediaHostAttachment(desiredLocation)
mediaCarouselController.onDesiredLocationChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 584908ff2aad..e57de09f1063 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -46,8 +46,8 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.surfaceeffects.PaintDrawCallback
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
@@ -82,7 +82,6 @@ constructor(
private val logger: MediaViewLogger,
private val seekBarViewModel: SeekBarViewModel,
@Main private val mainExecutor: DelayableExecutor,
- private val mediaFlags: MediaFlags,
private val globalSettings: GlobalSettings,
) {
@@ -125,7 +124,7 @@ constructor(
set(value) {
if (field != value) {
field = value
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
locationChangeListener(value)
}
}
@@ -212,7 +211,7 @@ constructor(
private val scrubbingChangeListener =
object : SeekBarViewModel.ScrubbingChangeListener {
override fun onScrubbingChanged(scrubbing: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
if (isScrubbing == scrubbing) return
isScrubbing = scrubbing
updateDisplayForScrubbingChange()
@@ -222,7 +221,7 @@ constructor(
private val enabledChangeListener =
object : SeekBarViewModel.EnabledChangeListener {
override fun onEnabledChanged(enabled: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
if (isSeekBarEnabled == enabled) return
isSeekBarEnabled = enabled
MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled)
@@ -238,7 +237,7 @@ constructor(
* @param listening True when player should be active. Otherwise, false.
*/
fun setListening(listening: Boolean) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
seekBarViewModel.listening = listening
}
@@ -272,7 +271,7 @@ constructor(
)
)
}
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
if (
this@MediaViewController::recsConfigurationChangeListener.isInitialized
) {
@@ -344,7 +343,7 @@ constructor(
* Notify this controller that the view has been removed and all listeners should be destroyed
*/
fun onDestroy() {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
if (this::seekBarObserver.isInitialized) {
seekBarViewModel.progress.removeObserver(seekBarObserver)
}
@@ -565,7 +564,7 @@ constructor(
state: MediaHostState?,
isGutsAnimation: Boolean = false
): TransitionViewState? {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
return obtainSceneContainerViewState()
}
@@ -667,7 +666,7 @@ constructor(
}
fun attachPlayer(mediaViewHolder: MediaViewHolder) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
this.mediaViewHolder = mediaViewHolder
// Setting up seek bar.
@@ -741,7 +740,7 @@ constructor(
}
fun updateAnimatorDurationScale() {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
if (this::seekBarObserver.isInitialized) {
seekBarObserver.animationEnabled =
globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f
@@ -801,7 +800,7 @@ constructor(
}
fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
this.recommendationViewHolder = recommendationViewHolder
attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION)
@@ -810,13 +809,13 @@ constructor(
}
fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
seekBarViewModel.logSeek = onSeek
onBindSeekBar(seekBarViewModel)
}
fun setUpTurbulenceNoise() {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
mediaViewHolder!!.let {
if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
turbulenceNoiseAnimationConfig =
@@ -1049,7 +1048,7 @@ constructor(
*/
private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? {
val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
return obtainSceneContainerViewState()
}
@@ -1080,7 +1079,7 @@ constructor(
/** Clear all existing measurements and refresh the state to match the view. */
fun refreshState() =
traceSection("MediaViewController#refreshState") {
- if (mediaFlags.isSceneContainerEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
// We don't need to recreate measurements for scene container, since it's a known
// size. Just get the view state and update the layout controller
obtainSceneContainerViewState()?.let {
@@ -1169,13 +1168,13 @@ constructor(
}
fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
isPrevButtonAvailable = isAvailable
prevNotVisibleValue = notVisibleValue
}
fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
- if (!mediaFlags.isSceneContainerEnabled()) return
+ if (!SceneContainerFlag.isEnabled) return
isNextButtonAvailable = isAvailable
nextNotVisibleValue = notVisibleValue
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 64820e0d0ced..f4601340ee42 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -30,6 +30,7 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
+import com.android.systemui.media.controls.shared.MediaControlDrawables
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaControlModel
@@ -284,9 +285,9 @@ class MediaControlViewModel(
},
cancelTextBackground =
if (model.isDismissible) {
- applicationContext.getDrawable(R.drawable.qs_media_outline_button)
+ MediaControlDrawables.getOutline(applicationContext)
} else {
- applicationContext.getDrawable(R.drawable.qs_media_solid_button)
+ MediaControlDrawables.getSolid(applicationContext)
},
onSettingsClicked = {
logger.logLongPressSettings(model.uid, model.packageName, model.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 21c311191710..a65243dfe315 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -21,7 +21,6 @@ import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import javax.inject.Inject
@SysUISingleton
@@ -49,7 +48,4 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClass
/** Check whether we allow remote media to generate resume controls */
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
-
- /** Check whether to use scene framework */
- fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 2dbe2aa4ef2d..bf2aa7efc0c4 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -20,7 +20,7 @@ import android.annotation.ColorInt
import android.annotation.UserIdInt
import android.app.ActivityManager.RecentTaskInfo
import android.content.ComponentName
-import com.android.wm.shell.util.SplitBounds
+import com.android.wm.shell.shared.split.SplitBounds
data class RecentTask(
val taskId: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 01b1be9d02b7..82e58cc7f1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -23,7 +23,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.util.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index dd1fa76c65c9..bb4d89457b58 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -36,10 +36,10 @@ import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
import com.android.systemui.res.R
import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import com.android.wm.shell.shared.split.SplitBounds
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
import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
import java.util.Optional
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 42f66cca2522..7d2a1e178dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -18,7 +18,6 @@ package com.android.systemui.model
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -107,10 +106,7 @@ constructor(
{
it.scene == Scenes.Lockscreen && it.invisibleDueToOcclusion
},
- SYSUI_STATE_COMMUNAL_HUB_SHOWING to
- {
- glanceableHubBackGesture() && it.scene == Scenes.Communal
- }
+ SYSUI_STATE_COMMUNAL_HUB_SHOWING to { it.scene == Scenes.Communal }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index ac878c2d698d..6f82d5dfff15 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -19,19 +19,24 @@ package com.android.systemui.navigationbar;
import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR;
import static android.app.StatusBarManager.WindowVisibleState;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON;
import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import android.content.ContentResolver;
import android.content.Context;
@@ -60,10 +65,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
@@ -107,6 +113,7 @@ public final class NavBarHelper implements
AccessibilityManager.AccessibilityServicesStateChangeListener,
AccessibilityButtonModeObserver.ModeChangedListener,
AccessibilityButtonTargetsObserver.TargetsChangedListener,
+ AccessibilityGestureTargetsObserver.TargetsChangedListener,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener {
private static final String TAG = NavBarHelper.class.getSimpleName();
@@ -122,6 +129,7 @@ public final class NavBarHelper implements
private final SystemActions mSystemActions;
private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
+ private final AccessibilityGestureTargetsObserver mAccessibilityGestureTargetsObserver;
private final List<NavbarTaskbarStateUpdater> mStateListeners = new ArrayList<>();
private final Context mContext;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -188,6 +196,7 @@ public final class NavBarHelper implements
public NavBarHelper(Context context, AccessibilityManager accessibilityManager,
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
+ AccessibilityGestureTargetsObserver accessibilityGestureTargetsObserver,
SystemActions systemActions,
OverviewProxyService overviewProxyService,
Lazy<AssistManager> assistManagerLazy,
@@ -220,6 +229,7 @@ public final class NavBarHelper implements
mSystemActions = systemActions;
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
+ mAccessibilityGestureTargetsObserver = accessibilityGestureTargetsObserver;
mWm = wm;
mDefaultDisplayId = displayTracker.getDefaultDisplayId();
mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
@@ -249,6 +259,7 @@ public final class NavBarHelper implements
mAccessibilityManager.addAccessibilityServicesStateChangeListener(this);
mAccessibilityButtonModeObserver.addListener(this);
mAccessibilityButtonTargetsObserver.addListener(this);
+ mAccessibilityGestureTargetsObserver.addListener(this);
// Setup assistant listener
mContentResolver.registerContentObserver(
@@ -291,6 +302,7 @@ public final class NavBarHelper implements
mAccessibilityManager.removeAccessibilityServicesStateChangeListener(this);
mAccessibilityButtonModeObserver.removeListener(this);
mAccessibilityButtonTargetsObserver.removeListener(this);
+ mAccessibilityGestureTargetsObserver.removeListener(this);
// Clean up assistant listeners
mContentResolver.unregisterContentObserver(mAssistContentObserver);
@@ -380,43 +392,50 @@ public final class NavBarHelper implements
}
@Override
+ public void onAccessibilityGestureTargetsChanged(String targets) {
+ updateA11yState();
+ }
+
+ @Override
public void onConfigChanged(Configuration newConfig) {
mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
}
+ private int getNumOfA11yShortcutTargetsForNavSystem() {
+ final int buttonMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
+ final int shortcutType;
+ if (!android.provider.Flags.a11yStandaloneGestureEnabled()) {
+ shortcutType = buttonMode
+ != ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU ? SOFTWARE : DEFAULT;
+ // If accessibility button is floating menu mode, there are no clickable targets.
+ } else {
+ if (mNavBarMode == NAV_BAR_MODE_GESTURAL) {
+ shortcutType = GESTURE;
+ } else {
+ shortcutType = buttonMode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR
+ ? SOFTWARE : DEFAULT;
+ }
+ }
+ return mAccessibilityManager.getAccessibilityShortcutTargets(shortcutType).size();
+ }
+
/**
* Updates the current accessibility button state. The accessibility button state is only
* used for {@link Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} and
* {@link Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}, otherwise it is reset to 0.
*/
- private void updateA11yState() {
+ @VisibleForTesting
+ void updateA11yState() {
final long prevState = mA11yButtonState;
final boolean clickable;
final boolean longClickable;
- if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
- == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
- // If accessibility button is floating menu mode, click and long click state should be
- // disabled.
- clickable = false;
- longClickable = false;
- mA11yButtonState = 0;
- } else {
- // AccessibilityManagerService resolves services for the current user since the local
- // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS
- // permission
- final List<String> a11yButtonTargets =
- mAccessibilityManager.getAccessibilityShortcutTargets(
- ShortcutConstants.UserShortcutType.SOFTWARE);
- final int requestingServices = a11yButtonTargets.size();
-
- clickable = requestingServices >= 1;
-
- // `longClickable` is used to determine whether to pop up the accessibility chooser
- // dialog or not, and it’s also only for multiple services.
- longClickable = requestingServices >= 2;
- mA11yButtonState = (clickable ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
- | (longClickable ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
- }
+ int clickableServices = getNumOfA11yShortcutTargetsForNavSystem();
+ clickable = clickableServices >= 1;
+ // `longClickable` is used to determine whether to pop up the accessibility chooser
+ // dialog or not, and it’s also only for multiple services.
+ longClickable = clickableServices >= 2;
+ mA11yButtonState = (clickable ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
+ | (longClickable ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
// Update the system actions if the state has changed
if (prevState != mA11yButtonState) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index cb0bb4abb423..e44069f78ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -563,10 +563,6 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
}
@Override
- public void onRecentsAnimationStateChanged(boolean running) {
- }
-
- @Override
public void onNavigationModeChanged(int mode) {
mNavigationMode = mode;
mEdgeBackGestureHandler.onNavigationModeChanged(mode);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 9f9c8e910f20..c3274b7ca28b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -37,12 +37,18 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.round
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -50,6 +56,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.modifiers.height
import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
import com.android.compose.theme.PlatformTheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,6 +66,7 @@ import com.android.systemui.media.dagger.MediaModule.QS_PANEL
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.composefragment.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -100,6 +108,17 @@ constructor(
private val qqsPositionOnRoot = Rect()
private val composeViewPositionOnScreen = Rect()
+ // Inside object for namespacing
+ private val notificationScrimClippingParams =
+ object {
+ var isEnabled by mutableStateOf(false)
+ var leftInset by mutableStateOf(0)
+ var rightInset by mutableStateOf(0)
+ var top by mutableStateOf(0)
+ var bottom by mutableStateOf(0)
+ var radius by mutableStateOf(0)
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -126,7 +145,18 @@ constructor(
AnimatedVisibility(
visible = visible,
- modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ modifier =
+ Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf(
+ notificationScrimClippingParams.isEnabled
+ ) {
+ Modifier.notificationScrimClip(
+ notificationScrimClippingParams.leftInset,
+ notificationScrimClippingParams.top,
+ notificationScrimClippingParams.rightInset,
+ notificationScrimClippingParams.bottom,
+ notificationScrimClippingParams.radius,
+ )
+ }
) {
AnimatedContent(targetState = qsState) {
when (it) {
@@ -239,7 +269,7 @@ constructor(
}
override fun setCollapseExpandAction(action: Runnable?) {
- // Nothing to do yet. But this should be wired to a11y
+ viewModel.collapseExpandAccessibilityAction = action
}
override fun getHeightDiff(): Int {
@@ -280,7 +310,16 @@ constructor(
cornerRadius: Int,
visible: Boolean,
fullWidth: Boolean
- ) {}
+ ) {
+ notificationScrimClippingParams.isEnabled = visible
+ notificationScrimClippingParams.top = top
+ notificationScrimClippingParams.bottom = bottom
+ // Full width means that QS will show in the entire width allocated to it (for example
+ // phone) vs. showing in a narrower column (for example, tablet portrait).
+ notificationScrimClippingParams.leftInset = if (fullWidth) 0 else leftInset
+ notificationScrimClippingParams.rightInset = if (fullWidth) 0 else rightInset
+ notificationScrimClippingParams.radius = cornerRadius
+ }
override fun isFullyCollapsed(): Boolean {
return viewModel.qsExpansionValue <= 0f
@@ -384,6 +423,9 @@ constructor(
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
}
.padding(top = { qqsPadding })
+ .collapseExpandSemanticAction(
+ stringResource(id = R.string.accessibility_quick_settings_expand)
+ )
)
Spacer(modifier = Modifier.weight(1f))
}
@@ -393,7 +435,12 @@ constructor(
private fun QuickSettingsElement() {
val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
- Column {
+ Column(
+ modifier =
+ Modifier.collapseExpandSemanticAction(
+ stringResource(id = R.string.accessibility_quick_settings_collapse)
+ )
+ ) {
Box(modifier = Modifier.fillMaxSize().weight(1f)) {
Column {
Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() })
@@ -409,6 +456,20 @@ constructor(
}
}
}
+
+ private fun Modifier.collapseExpandSemanticAction(label: String): Modifier {
+ return viewModel.collapseExpandAccessibilityAction?.let {
+ semantics {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(label) {
+ it.run()
+ true
+ }
+ )
+ }
+ } ?: this
+ }
}
private fun View.setBackPressedDispatcher() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
new file mode 100644
index 000000000000..93c6445b78ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.ui
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ClipOp
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.asAndroidPath
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+
+/**
+ * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
+ * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
+ * from the QS container.
+ */
+fun Modifier.notificationScrimClip(
+ leftInset: Int,
+ top: Int,
+ rightInset: Int,
+ bottom: Int,
+ radius: Int
+): Modifier {
+ return this then NotificationScrimClipElement(leftInset, top, rightInset, bottom, radius)
+}
+
+private class NotificationScrimClipNode(
+ var leftInset: Float,
+ var top: Float,
+ var rightInset: Float,
+ var bottom: Float,
+ var radius: Float,
+) : DrawModifierNode, Modifier.Node() {
+ private val path = Path()
+
+ var invalidated = true
+
+ override fun ContentDrawScope.draw() {
+ if (invalidated) {
+ path.rewind()
+ path
+ .asAndroidPath()
+ .addRoundRect(
+ -leftInset,
+ top,
+ size.width + rightInset,
+ bottom,
+ radius,
+ radius,
+ android.graphics.Path.Direction.CW
+ )
+ invalidated = false
+ }
+ clipPath(path, ClipOp.Difference) { this@draw.drawContent() }
+ }
+}
+
+private data class NotificationScrimClipElement(
+ val leftInset: Int,
+ val top: Int,
+ val rightInset: Int,
+ val bottom: Int,
+ val radius: Int,
+) : ModifierNodeElement<NotificationScrimClipNode>() {
+ override fun create(): NotificationScrimClipNode {
+ return NotificationScrimClipNode(
+ leftInset.toFloat(),
+ top.toFloat(),
+ rightInset.toFloat(),
+ bottom.toFloat(),
+ radius.toFloat(),
+ )
+ }
+
+ override fun update(node: NotificationScrimClipNode) {
+ val changed =
+ node.leftInset != leftInset.toFloat() ||
+ node.top != top.toFloat() ||
+ node.rightInset != rightInset.toFloat() ||
+ node.bottom != bottom.toFloat() ||
+ node.radius != radius.toFloat()
+ if (changed) {
+ node.leftInset = leftInset.toFloat()
+ node.top = top.toFloat()
+ node.rightInset = rightInset.toFloat()
+ node.bottom = bottom.toFloat()
+ node.radius = radius.toFloat()
+ node.invalidated = true
+ }
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "notificationScrimClip"
+ properties["leftInset"] = leftInset
+ properties["top"] = top
+ properties["rightInset"] = rightInset
+ properties["bottom"] = bottom
+ properties["radius"] = radius
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 7d52216a4d2e..df77878b88d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -19,21 +19,26 @@ package com.android.systemui.qs.composefragment.viewmodel
import android.content.res.Resources
import android.graphics.Rect
import androidx.annotation.FloatRange
+import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleCoroutineScope
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
+import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.util.LargeScreenUtils
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -140,7 +145,34 @@ constructor(
private val _keyguardAndExpanded = MutableStateFlow(false)
- private val _statusBarState = MutableStateFlow(-1)
+ /**
+ * Tracks the current [StatusBarState]. It will switch early if the upcoming state is
+ * [StatusBarState.KEYGUARD]
+ */
+ @get:VisibleForTesting
+ val statusBarState =
+ conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ trySend(newState)
+ }
+
+ override fun onUpcomingStateChanged(upcomingState: Int) {
+ if (upcomingState == StatusBarState.KEYGUARD) {
+ trySend(upcomingState)
+ }
+ }
+ }
+ sysuiStatusBarStateController.addCallback(callback)
+
+ awaitClose { sysuiStatusBarStateController.removeCallback(callback) }
+ }
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ sysuiStatusBarStateController.state,
+ )
private val _viewHeight = MutableStateFlow(0)
@@ -186,6 +218,12 @@ constructor(
}
.stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS)
+ /**
+ * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for
+ * determining the correct action based on the expansion state.
+ */
+ var collapseExpandAccessibilityAction: Runnable? = null
+
@AssistedFactory
interface Factory {
fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 6dc101a63f09..b0d4fa26225e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -42,6 +42,8 @@ import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
import kotlin.math.max
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -145,10 +147,11 @@ class FooterActionsViewModel(
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
fun create(lifecycleCoroutineScope: LifecycleCoroutineScope): FooterActionsViewModel {
val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
if (lifecycleCoroutineScope.isActive) {
- lifecycleCoroutineScope.launch {
+ lifecycleCoroutineScope.launch(start = CoroutineStart.ATOMIC) {
try {
awaitCancellation()
} finally {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 0b9cd96670b1..9a2315be29a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -158,18 +158,20 @@ private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
return item.span != 1 && offset.x > itemCenter.x
}
+@Composable
fun Modifier.dragAndDropTileSource(
sizedTile: SizedTile<EditTileViewModel>,
onTap: (TileSpec) -> Unit,
onDoubleTap: (TileSpec) -> Unit,
dragAndDropState: DragAndDropState
): Modifier {
+ val state by rememberUpdatedState(dragAndDropState)
return dragAndDropSource {
detectTapGestures(
onTap = { onTap(sizedTile.tile.tileSpec) },
onDoubleTap = { onDoubleTap(sizedTile.tile.tileSpec) },
onLongPress = {
- dragAndDropState.onStarted(sizedTile)
+ state.onStarted(sizedTile)
// The tilespec from the ClipData transferred isn't actually needed as we're moving
// a tile within the same application. We're using a custom MIME type to limit the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 6eacb2ef9f14..79c2eb90af20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -24,6 +24,7 @@ import android.service.quicksettings.Tile.STATE_INACTIVE
import android.text.TextUtils
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
@@ -81,6 +82,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.LocalContext
@@ -132,11 +134,16 @@ fun Tile(
val uiState = remember(state) { state.toUiState() }
val colors = TileDefaults.getColorForState(uiState)
+ // TODO(b/361789146): Draw the shapes instead of clipping
+ val tileShape = TileDefaults.animateTileShape(uiState.state)
+ val iconShape = TileDefaults.animateIconShape(uiState.state)
+
TileContainer(
colors = colors,
showLabels = showLabels,
label = uiState.label,
iconOnly = iconOnly,
+ shape = if (iconOnly) iconShape else tileShape,
clickEnabled = true,
onClick = tile::onClick,
onLongClick = tile::onLongClick,
@@ -151,6 +158,7 @@ fun Tile(
secondaryLabel = uiState.secondaryLabel,
icon = icon,
colors = colors,
+ iconShape = iconShape,
toggleClickSupported = state.handlesSecondaryClick,
onClick = {
if (state.handlesSecondaryClick) {
@@ -169,6 +177,7 @@ private fun TileContainer(
showLabels: Boolean,
label: String,
iconOnly: Boolean,
+ shape: Shape,
clickEnabled: Boolean = false,
onClick: (Expandable) -> Unit = {},
onLongClick: (Expandable) -> Unit = {},
@@ -189,10 +198,8 @@ private fun TileContainer(
}
Expandable(
color = backgroundColor,
- shape = TileDefaults.TileShape,
- modifier =
- Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
- .clip(TileDefaults.TileShape)
+ shape = shape,
+ modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)).clip(shape)
) {
Box(
modifier =
@@ -227,6 +234,7 @@ private fun LargeTileContent(
secondaryLabel: String?,
icon: Icon,
colors: TileColors,
+ iconShape: Shape,
toggleClickSupported: Boolean = false,
onClick: () -> Unit = {},
onLongClick: () -> Unit = {},
@@ -239,7 +247,7 @@ private fun LargeTileContent(
Box(
modifier =
Modifier.fillMaxHeight().aspectRatio(1f).thenIf(toggleClickSupported) {
- Modifier.clip(TileDefaults.TileShape)
+ Modifier.clip(iconShape)
.background(colors.iconBackground, { 1f })
.combinedClickable(onClick = onClick, onLongClick = onLongClick)
}
@@ -391,7 +399,7 @@ private fun RemoveTileTarget() {
horizontalArrangement = tileHorizontalArrangement(),
modifier =
Modifier.fillMaxHeight()
- .border(1.dp, LocalContentColor.current, shape = TileDefaults.TileShape)
+ .border(1.dp, LocalContentColor.current, shape = CircleShape)
.padding(10.dp)
) {
Icon(imageVector = Icons.Default.Clear, contentDescription = null)
@@ -533,7 +541,7 @@ fun LazyGridScope.editTiles(
Modifier.background(
color = MaterialTheme.colorScheme.secondary,
alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
- shape = TileDefaults.TileShape
+ shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius)
)
.animateItem()
)
@@ -619,6 +627,7 @@ fun EditTile(
showLabels = showLabels,
label = label,
iconOnly = iconOnly,
+ shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
modifier = modifier,
) {
if (iconOnly) {
@@ -633,6 +642,7 @@ fun EditTile(
secondaryLabel = tileViewModel.appName?.load(),
icon = tileViewModel.icon,
colors = colors,
+ iconShape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
)
}
}
@@ -736,7 +746,9 @@ private object EditModeTileDefaults {
}
private object TileDefaults {
- val TileShape = CircleShape
+ val InactiveCornerRadius = 50.dp
+ val ActiveIconCornerRadius = 16.dp
+ val ActiveTileCornerRadius = 24.dp
val IconTileWithLabelHeight = 140.dp
/** An active tile without dual target uses the active color as background */
@@ -795,6 +807,39 @@ private object TileDefaults {
else -> unavailableTileColors()
}
}
+
+ @Composable
+ fun animateIconShape(state: Int): Shape {
+ return animateShape(
+ state = state,
+ activeCornerRadius = ActiveTileCornerRadius,
+ label = "QSTileCornerRadius",
+ )
+ }
+
+ @Composable
+ fun animateTileShape(state: Int): Shape {
+ return animateShape(
+ state = state,
+ activeCornerRadius = ActiveIconCornerRadius,
+ label = "QSTileIconCornerRadius",
+ )
+ }
+
+ @Composable
+ fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape {
+ val animatedCornerRadius by
+ animateDpAsState(
+ targetValue =
+ if (state == STATE_ACTIVE) {
+ activeCornerRadius
+ } else {
+ InactiveCornerRadius
+ },
+ label = label
+ )
+ return RoundedCornerShape(animatedCornerRadius)
+ }
}
private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 8887f5857baf..9abc494e56e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -71,6 +71,7 @@ import com.android.systemui.qs.SideLabelTileLayout;
import com.android.systemui.qs.logging.QSLogger;
import java.io.PrintWriter;
+import java.util.Objects;
/**
* Base quick-settings tile, extend this to create a new tile.
@@ -350,6 +351,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
public void userSwitch(int newUserId) {
mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
+ postStale();
}
public void destroy() {
@@ -667,6 +669,18 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
public String toString() {
return "DrawableIcon";
}
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ // No need to compare equality of the mInvisibleDrawable as that's generated from
+ // mDrawable's constant state.
+ return other instanceof DrawableIcon && ((DrawableIcon) other).mDrawable == mDrawable;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDrawable);
+ }
}
public static class DrawableIconWithRes extends DrawableIcon {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 5f5b26514de8..3f18fc2066eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -57,7 +57,7 @@ constructor(
isActivated = modes.isNotEmpty(),
icon =
if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons())
- zenModeInteractor.getActiveModeIcon(context, modes)
+ zenModeInteractor.getActiveModeIcon(modes)
else null,
activeModes = modes.map { it.name }
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 0e127e3f6bb4..83c3335ebffb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -41,10 +41,11 @@ constructor(
if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() && data.icon != null) {
icon = { data.icon }
} else {
- val defaultIconRes =
+ val iconRes =
if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off
- iconRes = defaultIconRes
- icon = { resources.getDrawable(defaultIconRes, theme).asIcon() }
+ val icon = resources.getDrawable(iconRes, theme).asIcon()
+ this.iconRes = iconRes
+ this.icon = { icon }
}
activationState =
if (data.isActivated) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
index 55b8f5f0ca98..12f3c9cd0b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
@@ -44,7 +44,7 @@ constructor(
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
val mediaCarouselInteractor: MediaCarouselInteractor,
-) : SysUiViewModel() {
+) : SysUiViewModel {
val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
index abfca4b9aa4a..cb99be48912e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
@@ -35,7 +35,7 @@ class QuickSettingsShadeSceneContentViewModel
constructor(
val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) : SysUiViewModel() {
+) : SysUiViewModel {
@AssistedFactory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ecf816b263ff..a37a722e13e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -26,7 +26,6 @@ import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.Flags.glanceableHubBackGesture;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -86,10 +85,10 @@ import com.android.internal.util.ScreenshotHelper;
import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
@@ -837,8 +836,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
.setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
.setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
.setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
- .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING,
- glanceableHubBackGesture() && communalShowing)
+ .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING, communalShowing)
.commitUpdate(mContext.getDisplayId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 863a899b6f4c..3d6d00eb3cc0 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -24,6 +24,7 @@ import android.content.res.Resources
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
+import android.provider.Settings
import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.DialogTransitionAnimator
@@ -90,7 +91,16 @@ constructor(
// ViewCapture needs to save it's data before it is disabled, or else the data will
// be lost. This is expected to change in the near future, and when that happens
// this line should be removed.
- bgExecutor.execute { traceurMessageSender.stopTracing() }
+ bgExecutor.execute {
+ if (issueRecordingState.traceConfig.longTrace) {
+ Settings.Global.putInt(
+ contentResolver,
+ NOTIFY_SESSION_ENDED_SETTING,
+ DISABLED
+ )
+ }
+ traceurMessageSender.stopTracing()
+ }
issueRecordingState.isRecording = false
}
ACTION_SHARE -> {
@@ -125,6 +135,8 @@ constructor(
companion object {
private const val TAG = "IssueRecordingService"
private const val CHANNEL_ID = "issue_record"
+ private const val NOTIFY_SESSION_ENDED_SETTING = "should_notify_trace_session_ended"
+ private const val DISABLED = 0
/**
* Get an intent to stop the issue recording service.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
index 2d510e1cb659..ea61bd32c1f2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -118,8 +118,11 @@ constructor(
get() =
when (this) {
is ObservableTransitionState.Idle -> currentScene.canBeOccluded
- is ObservableTransitionState.Transition ->
+ is ObservableTransitionState.Transition.ChangeCurrentScene ->
fromScene.canBeOccluded && toScene.canBeOccluded
+ is ObservableTransitionState.Transition.ReplaceOverlay,
+ is ObservableTransitionState.Transition.ShowOrHideOverlay ->
+ TODO("b/359173565: Handle overlay transitions")
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 75cb017db5c1..1b9c346129c6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -109,7 +109,15 @@ constructor(
*/
val transitioningTo: StateFlow<SceneKey?> =
transitionState
- .map { state -> (state as? ObservableTransitionState.Transition)?.toScene }
+ .map { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> null
+ is ObservableTransitionState.Transition.ChangeCurrentScene -> state.toScene
+ is ObservableTransitionState.Transition.ShowOrHideOverlay,
+ is ObservableTransitionState.Transition.ReplaceOverlay ->
+ TODO("b/359173565: Handle overlay transitions")
+ }
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
index c6f51b384a0a..ec743ba5c91e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
@@ -112,7 +112,7 @@ constructor(
// It
// happens only when unlocking or when dismissing a dismissible lockscreen.
val isTransitioningAwayFromKeyguard =
- transitionState is ObservableTransitionState.Transition &&
+ transitionState is ObservableTransitionState.Transition.ChangeCurrentScene &&
transitionState.fromScene.isKeyguard() &&
transitionState.toScene == Scenes.Gone
@@ -120,7 +120,7 @@ constructor(
val isCurrentSceneShade = currentScene.isShade()
// This is true when moving into one of the shade scenes when a non-shade scene.
val isTransitioningToShade =
- transitionState is ObservableTransitionState.Transition &&
+ transitionState is ObservableTransitionState.Transition.ChangeCurrentScene &&
!transitionState.fromScene.isShade() &&
transitionState.toScene.isShade()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 61a06dbff18f..8e2e8a1d521b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -20,7 +20,6 @@ import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.lifecycle.Activatable
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
/**
@@ -36,10 +35,6 @@ interface Scene : Activatable {
/** Uniquely-identifying key for this scene. The key must be unique within its container. */
val key: SceneKey
- override suspend fun activate(): Nothing {
- awaitCancellation()
- }
-
/**
* The mapping between [UserAction] and destination [UserActionResult]s.
*
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
index b5de1b6209bb..9144f16d9251 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.scene.ui.viewmodel
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,7 +33,7 @@ import kotlinx.coroutines.flow.asStateFlow
* need to worry about resetting the value of [actions] when the view-model is deactivated/canceled,
* this base class takes care of it.
*/
-abstract class SceneActionsViewModel : SysUiViewModel() {
+abstract class SceneActionsViewModel : SysUiViewModel, ExclusiveActivatable() {
private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
/**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index f8a9f8c5a279..9dfb7450fd3f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -24,6 +24,8 @@ import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -32,7 +34,6 @@ import com.android.systemui.scene.shared.model.Scenes
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -45,7 +46,7 @@ constructor(
private val powerInteractor: PowerInteractor,
private val logger: SceneLogger,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
/**
* Keys of all scenes in the container.
*
@@ -57,8 +58,10 @@ constructor(
/** The scene that should be rendered. */
val currentScene: StateFlow<SceneKey> = sceneInteractor.currentScene
+ private val hydrator = Hydrator()
+
/** Whether the container is visible. */
- val isVisible: Boolean by hydratedStateOf(sceneInteractor.isVisible)
+ val isVisible: Boolean by hydrator.hydratedStateOf(sceneInteractor.isVisible)
override suspend fun onActivated(): Nothing {
try {
@@ -75,7 +78,8 @@ constructor(
}
}
)
- awaitCancellation()
+
+ hydrator.activate()
} finally {
// Clears the previously-sent MotionEventHandler so the owner of the view-model releases
// their reference to it.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
index 7f8c1463ed1f..706797d9bbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -37,7 +37,7 @@ constructor(
private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
@Main private val resources: Resources,
val sliderControllerFactory: BrightnessSliderController.Factory,
-) : SysUiViewModel(), MirrorController {
+) : SysUiViewModel, MirrorController {
private val tempPosition = IntArray(2)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 181c3df92222..4639e2235346 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade
import android.content.Context
-import android.graphics.Insets
import android.graphics.Rect
import android.os.PowerManager
import android.os.SystemClock
@@ -26,13 +25,14 @@ import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
-import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
@@ -40,7 +40,6 @@ import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Flags
-import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.communal.dagger.Communal
@@ -55,6 +54,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalTouchLog
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -91,8 +93,10 @@ constructor(
@Communal private val dataSourceDelegator: SceneDataSourceDelegator,
private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
private val keyguardMediaController: KeyguardMediaController,
- private val lockscreenSmartspaceController: LockscreenSmartspaceController
+ private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+ @CommunalTouchLog logBuffer: LogBuffer,
) : LifecycleOwner {
+ private val logger = Logger(logBuffer, "GlanceableHubContainerController")
private class CommunalWrapper(context: Context) : FrameLayout(context) {
private val consumers: MutableSet<Consumer<Boolean>> = ArraySet()
@@ -143,6 +147,17 @@ constructor(
private var isTrackingHubTouch = false
/**
+ * True if a touch gesture on the lock screen has been consumed by the shade/bouncer and thus
+ * should be ignored by the hub.
+ *
+ * This is necessary on the lock screen as gestures on an empty spot go through special touch
+ * handling logic in [NotificationShadeWindowViewController] that decides if they should go to
+ * the shade or bouncer. Once the shade or bouncer are moving, we don't get the typical cancel
+ * event so to play nice, we ignore touches once we see the shade or bouncer are opening.
+ */
+ private var touchTakenByKeyguardGesture = false
+
+ /**
* True if the hub UI is fully open, meaning it should receive touch input.
*
* Tracks [CommunalInteractor.isCommunalShowing].
@@ -206,6 +221,21 @@ constructor(
*/
private var isDreaming = false
+ /** Observes and logs state when the lifecycle that controls the [touchMonitor] updates. */
+ private val touchLifecycleLogger: LifecycleObserver = LifecycleEventObserver { _, event ->
+ logger.d({
+ "Touch handler lifecycle changed to $str1. hubShowing: $bool1, " +
+ "shadeShowingAndConsumingTouches: $bool2, " +
+ "anyBouncerShowing: $bool3, inEditModeTransition: $bool4"
+ }) {
+ str1 = event.toString()
+ bool1 = hubShowing
+ bool2 = shadeShowingAndConsumingTouches
+ bool3 = anyBouncerShowing
+ bool4 = inEditModeTransition
+ }
+ }
+
/** Returns a flow that tracks whether communal hub is available. */
fun communalAvailable(): Flow<Boolean> =
anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
@@ -268,6 +298,7 @@ constructor(
init()
}
}
+ lifecycleRegistry.addObserver(touchLifecycleLogger)
lifecycleRegistry.currentState = Lifecycle.State.CREATED
communalContainerView = containerView
@@ -288,21 +319,13 @@ constructor(
// Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
// occluded.
lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
- // Avoid adding exclusion to end/start edges to allow back gestures.
- val insets =
- if (glanceableHubBackGesture()) {
- containerView.rootWindowInsets.getInsets(WindowInsets.Type.systemGestures())
- } else {
- Insets.NONE
- }
-
val ltr = containerView.layoutDirection == View.LAYOUT_DIRECTION_LTR
val backGestureInset =
Rect(
- if (ltr) 0 else insets.left,
0,
- if (ltr) insets.right else containerView.right,
+ 0,
+ if (ltr) 0 else containerView.right,
containerView.bottom,
)
@@ -318,9 +341,9 @@ constructor(
// Only allow swipe up to bouncer and swipe down to shade in the very
// top/bottom to avoid conflicting with widgets in the hub grid.
Rect(
- insets.left,
+ 0,
topEdgeSwipeRegionWidth,
- containerView.right - insets.right,
+ containerView.right,
containerView.bottom - bottomEdgeSwipeRegionWidth
),
// Disable back gestures on the left side of the screen, to avoid
@@ -328,6 +351,9 @@ constructor(
backGestureInset
)
}
+ logger.d({ "Insets updated: $str1" }) {
+ str1 = containerView.systemGestureExclusionRects.toString()
+ }
}
}
@@ -343,6 +369,9 @@ constructor(
),
{
anyBouncerShowing = it
+ if (hubShowing) {
+ logger.d({ "New value for anyBouncerShowing: $bool1" }) { bool1 = it }
+ }
updateTouchHandlingState()
}
)
@@ -396,7 +425,13 @@ constructor(
// If the shade reaches full expansion without interaction, then we should allow it
// to consume touches rather than handling it here until it disappears.
shadeShowingAndConsumingTouches =
- userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive
+ (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive).also {
+ if (it != shadeShowingAndConsumingTouches && hubShowing) {
+ logger.d({ "New value for shadeShowingAndConsumingTouches: $bool1" }) {
+ bool1 = it
+ }
+ }
+ }
updateTouchHandlingState()
}
)
@@ -404,6 +439,7 @@ constructor(
communalContainerWrapper = CommunalWrapper(containerView.context)
communalContainerWrapper?.addView(communalContainerView)
+ logger.d("Hub container initialized")
return communalContainerWrapper!!
}
@@ -446,6 +482,10 @@ constructor(
(it.parent as ViewGroup).removeView(it)
communalContainerWrapper = null
}
+
+ lifecycleRegistry.removeObserver(touchLifecycleLogger)
+
+ logger.d("Hub container disposed")
}
/**
@@ -463,15 +503,20 @@ constructor(
// In the case that we are handling full swipes on the lockscreen, are on the lockscreen,
// and the touch is within the horizontal notification band on the screen, do not process
// the touch.
- if (
- !hubShowing &&
- (!notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) ||
- keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) ||
- lockscreenSmartspaceController.isWithinSmartspaceBounds(
- ev.x.toInt(),
- ev.y.toInt()
- ))
- ) {
+ val touchOnNotifications =
+ !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y)
+ val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt())
+ val touchOnSmartspace =
+ lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt())
+ if (!hubShowing && (touchOnNotifications || touchOnUmo || touchOnSmartspace)) {
+ logger.d({
+ "Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
+ "touchOnSmartspace: $bool3"
+ }) {
+ bool1 = touchOnNotifications
+ bool2 = touchOnUmo
+ bool3 = touchOnSmartspace
+ }
return false
}
@@ -487,12 +532,56 @@ constructor(
val hubOccluded = anyBouncerShowing || shadeShowingAndConsumingTouches
if ((isDown || isMove) && !hubOccluded) {
+ if (isDown) {
+ logger.d({
+ "Touch started. x: $int1, y: $int2, hubShowing: $bool1, isDreaming: $bool2, " +
+ "onLockscreen: $bool3"
+ }) {
+ int1 = ev.x.toInt()
+ int2 = ev.y.toInt()
+ bool1 = hubShowing
+ bool2 = isDreaming
+ bool3 = onLockscreen
+ }
+ }
isTrackingHubTouch = true
}
if (isTrackingHubTouch) {
+ // On the lock screen, our touch handlers are not active and we rely on the NSWVC's
+ // touch handling for gestures on blank areas, which can go up to show the bouncer or
+ // down to show the notification shade. We see the touches first and they are not
+ // consumed and cancelled like on the dream or hub so we have to gracefully ignore them
+ // if the shade or bouncer are handling them. This issue only applies to touches on the
+ // keyguard itself, once the bouncer or shade are fully open, our logic stops us from
+ // taking touches.
+ touchTakenByKeyguardGesture =
+ (onLockscreen && (shadeConsumingTouches || anyBouncerShowing)).also {
+ if (it != touchTakenByKeyguardGesture && it) {
+ logger.d(
+ "Lock screen touch consumed by shade or bouncer, ignoring " +
+ "subsequent touches"
+ )
+ }
+ }
if (isUp || isCancel) {
+ logger.d({
+ val endReason = if (bool1) "up" else "cancel"
+ "Touch ended with $endReason. x: $int1, y: $int2, " +
+ "shadeConsumingTouches: $bool2, anyBouncerShowing: $bool3"
+ }) {
+ int1 = ev.x.toInt()
+ int2 = ev.y.toInt()
+ bool1 = isUp
+ bool2 = shadeConsumingTouches
+ bool3 = anyBouncerShowing
+ }
isTrackingHubTouch = false
+
+ // Clear out touch taken state to ensure the up/cancel event still gets dispatched
+ // to the hub. This is necessary as the hub always receives at least the initial
+ // down even if the shade or bouncer end up handling the touch.
+ touchTakenByKeyguardGesture = false
}
return dispatchTouchEvent(ev)
}
@@ -513,21 +602,8 @@ constructor(
return true
}
try {
- // On the lock screen, our touch handlers are not active and we rely on the NSWVC's
- // touch handling for gestures on blank areas, which can go up to show the bouncer or
- // down to show the notification shade. We see the touches first and they are not
- // consumed and cancelled like on the dream or hub so we have to gracefully ignore them
- // if the shade or bouncer are handling them. This issue only applies to touches on the
- // keyguard itself, once the bouncer or shade are fully open, our logic stops us from
- // taking touches.
- val touchTaken = onLockscreen && (shadeConsumingTouches || anyBouncerShowing)
-
- // Only dispatch touches to communal if not already handled or the touch is ending,
- // meaning the event is an up or cancel. This is necessary as the hub always receives at
- // least the initial down even if the shade or bouncer end up handling the touch.
- val dispatchToCommunal = !touchTaken || !isTrackingHubTouch
var handled = false
- if (dispatchToCommunal) {
+ if (!touchTakenByKeyguardGesture) {
communalContainerWrapper?.dispatchTouchEvent(ev) {
if (it) {
handled = true
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 8006e9421f5c..7d6712166a21 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -64,7 +64,7 @@ constructor(
0f
}
)
- is ObservableTransitionState.Transition ->
+ is ObservableTransitionState.Transition.ChangeCurrentScene ->
when {
state.fromScene == Scenes.Gone ->
if (state.toScene.isExpandable()) {
@@ -88,6 +88,9 @@ constructor(
}
else -> flowOf(1f)
}
+ is ObservableTransitionState.Transition.ShowOrHideOverlay,
+ is ObservableTransitionState.Transition.ReplaceOverlay ->
+ TODO("b/359173565: Handle overlay transitions")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
index 00c023540da3..25ae44ef8cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
@@ -37,7 +38,7 @@ import kotlinx.coroutines.flow.collectLatest
class OverlayShadeViewModel
@AssistedInject
constructor(private val sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor) :
- SysUiViewModel() {
+ SysUiViewModel, ExclusiveActivatable() {
private val _backgroundScene = MutableStateFlow(Scenes.Lockscreen)
/** The scene to show in the background when the overlay shade is open. */
val backgroundScene: StateFlow<SceneKey> = _backgroundScene.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index f0e9d41c0fe7..edfe79ad91b8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -24,6 +24,7 @@ import android.icu.text.DisplayContext
import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
@@ -65,7 +66,7 @@ constructor(
private val privacyChipInteractor: PrivacyChipInteractor,
private val clockInteractor: ShadeHeaderClockInteractor,
private val broadcastDispatcher: BroadcastDispatcher,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
/** True if there is exactly one mobile connection. */
val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index fe3bcb5c326c..f0f2a65d9abb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -20,6 +20,7 @@ package com.android.systemui.shade.ui.viewmodel
import androidx.lifecycle.LifecycleOwner
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
@@ -61,7 +62,7 @@ constructor(
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val sceneInteractor: SceneInteractor,
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index a1477b57a22a..f88fd7d00a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -1174,7 +1174,7 @@ public class CommandQueue extends IStatusBar.Stub implements
}
}
- @Override
+ // This was previously called from WM, but is now called from WMShell
public void onRecentsAnimationStateChanged(boolean running) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_RECENTS_ANIMATION_STATE_CHANGED, running ? 1 : 0, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 3068460f1cc5..6eadd2627399 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -471,17 +471,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
*/
private Drawable getIcon(Context sysuiContext,
Context context, StatusBarIcon statusBarIcon) {
- int userId = statusBarIcon.user.getIdentifier();
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- }
-
- // Try to load the monochrome app icon if applicable
- Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
- // Otherwise, just use the icon normally
- if (icon == null) {
- icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
- }
+ Drawable icon = loadDrawable(context, statusBarIcon);
TypedValue typedValue = new TypedValue();
sysuiContext.getResources().getValue(R.dimen.status_bar_icon_scale_factor,
@@ -509,6 +499,26 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
}
@Nullable
+ private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) {
+ if (usesModeIcons() && statusBarIcon.preloadedIcon != null) {
+ return statusBarIcon.preloadedIcon.mutate();
+ } else {
+ int userId = statusBarIcon.user.getIdentifier();
+ if (userId == UserHandle.USER_ALL) {
+ userId = UserHandle.USER_SYSTEM;
+ }
+
+ // Try to load the monochrome app icon if applicable
+ Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
+ // Otherwise, just use the icon normally
+ if (icon == null) {
+ icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
+ }
+ return icon;
+ }
+ }
+
+ @Nullable
private Drawable maybeGetMonochromeAppIcon(Context context,
StatusBarIcon statusBarIcon) {
if (android.app.Flags.notificationsUseMonochromeAppIcon()
@@ -1020,4 +1030,9 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
public boolean showsConversation() {
return mShowsConversation;
}
+
+ private static boolean usesModeIcons() {
+ return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+ && android.app.Flags.modesUiIcons();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index f74c9a6a209f..e9292f8c3cb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -79,6 +79,7 @@ internal constructor(
// NOTE: NotificationEntry.isClearable will internally check group children to ensure
// the group itself definitively clearable.
val isClearable = !isSensitiveContentProtectionActive && entry.isClearable
+ && !entry.isSensitive.value
when {
isSilent && isClearable -> hasClearableSilentNotifs = true
isSilent && !isClearable -> hasNonClearableSilentNotifs = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 17f401ac0dde..0efd5f15cb09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -45,7 +45,6 @@ import android.provider.Settings.Global.HEADS_UP_OFF
import android.service.notification.Flags
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -279,7 +278,8 @@ class AvalancheSuppressor(
private val packageManager: PackageManager,
private val uiEventLogger: UiEventLogger,
private val context: Context,
- private val notificationManager: NotificationManager
+ private val notificationManager: NotificationManager,
+ private val logger: VisualInterruptionDecisionLogger
) :
VisualInterruptionFilter(
types = setOf(PEEK, PULSE),
@@ -354,15 +354,18 @@ class AvalancheSuppressor(
override fun shouldSuppress(entry: NotificationEntry): Boolean {
if (!isCooldownEnabled()) {
+ logger.logAvalancheAllow("cooldown OFF")
return false
}
val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime
val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs
if (timedOut) {
+ logger.logAvalancheAllow("timedOut! timeSinceAvalancheMs=$timeSinceAvalancheMs")
return false
}
val state = calculateState(entry)
if (state != State.SUPPRESS) {
+ logger.logAvalancheAllow("state=$state")
return false
}
if (shouldShowEdu()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
index c204ea9097de..b83259dce298 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
@@ -93,6 +93,15 @@ constructor(@NotificationInterruptLog val buffer: LogBuffer) {
}
)
}
+
+ fun logAvalancheAllow(info: String) {
+ buffer.log(
+ TAG,
+ INFO,
+ { str1 = info },
+ { "AvalancheSuppressor: $str1" }
+ )
+ }
}
private const val TAG = "VisualInterruptionDecisionProvider"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 8e8d9b69ac58..2f8711a586ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -194,7 +194,8 @@ constructor(
packageManager,
uiEventLogger,
context,
- notificationManager
+ notificationManager,
+ logger
)
)
avalancheProvider.register()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 7119145a1fa8..48c974a33f12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -117,6 +117,8 @@ public class NotificationContentView extends FrameLayout implements Notification
protected HybridNotificationView mSingleLineView;
@Nullable public DisposableHandle mContractedBinderHandle;
+ @Nullable public DisposableHandle mExpandedBinderHandle;
+ @Nullable public DisposableHandle mHeadsUpBinderHandle;
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index a5cd2a2de085..c342bcd2706b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -46,6 +46,9 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -286,11 +289,15 @@ constructor(
}
FLAG_CONTENT_VIEW_EXPANDED ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) {
+ row.privateLayout.mExpandedBinderHandle?.dispose()
+ row.privateLayout.mExpandedBinderHandle = null
row.privateLayout.setExpandedChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
}
FLAG_CONTENT_VIEW_HEADS_UP ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) {
+ row.privateLayout.mHeadsUpBinderHandle?.dispose()
+ row.privateLayout.mHeadsUpBinderHandle = null
row.privateLayout.setHeadsUpChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
row.privateLayout.setHeadsUpInflatedSmartReplies(null)
@@ -499,17 +506,87 @@ constructor(
}
}
- if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) {
+ val richOngoingContentModel = inflationProgress.contentModel.richOngoingContentModel
+
+ if (
+ richOngoingContentModel != null &&
+ reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
+ ) {
logger.logAsyncTaskProgress(entry, "inflating RON view")
- inflationProgress.richOngoingNotificationViewHolder =
- inflationProgress.contentModel.richOngoingContentModel?.let {
+ val inflateContractedView = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0
+ val inflateExpandedView = reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0
+ val inflateHeadsUpView = reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0
+
+ inflationProgress.contractedRichOngoingNotificationViewHolder =
+ if (inflateContractedView) {
ronInflater.inflateView(
- contentModel = it,
+ contentModel = richOngoingContentModel,
existingView = row.privateLayout.contractedChild,
entry = entry,
systemUiContext = context,
- parentView = row.privateLayout
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.Contracted
+ )
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.contractedChild,
+ viewType = RichOngoingNotificationViewType.Contracted
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
+ }
+
+ inflationProgress.expandedRichOngoingNotificationViewHolder =
+ if (inflateExpandedView) {
+ ronInflater.inflateView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.expandedChild,
+ entry = entry,
+ systemUiContext = context,
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.Expanded
)
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.expandedChild,
+ viewType = RichOngoingNotificationViewType.Expanded
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
+ }
+
+ inflationProgress.headsUpRichOngoingNotificationViewHolder =
+ if (inflateHeadsUpView) {
+ ronInflater.inflateView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.headsUpChild,
+ entry = entry,
+ systemUiContext = context,
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.HeadsUp
+ )
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.headsUpChild,
+ viewType = RichOngoingNotificationViewType.HeadsUp
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
}
}
@@ -618,7 +695,9 @@ constructor(
var inflatedSmartReplyState: InflatedSmartReplyState? = null
var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
- var richOngoingNotificationViewHolder: InflatedContentViewHolder? = null
+ var contractedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
+ var expandedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
+ var headsUpRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
// Inflated SingleLineView that lacks the UI State
var inflatedSingleLineView: HybridNotificationView? = null
@@ -1428,14 +1507,21 @@ constructor(
logger.logAsyncTaskProgress(entry, "finishing")
// before updating the content model, stop existing binding if necessary
- val hasRichOngoingContentModel = result.contentModel.richOngoingContentModel != null
- val requestedRichOngoing = reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
- val rejectedRichOngoing = requestedRichOngoing && !hasRichOngoingContentModel
- if (result.richOngoingNotificationViewHolder != null || rejectedRichOngoing) {
+ if (result.contractedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
row.privateLayout.mContractedBinderHandle?.dispose()
row.privateLayout.mContractedBinderHandle = null
}
+ if (result.expandedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
+ row.privateLayout.mExpandedBinderHandle?.dispose()
+ row.privateLayout.mExpandedBinderHandle = null
+ }
+
+ if (result.headsUpRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
+ row.privateLayout.mHeadsUpBinderHandle?.dispose()
+ row.privateLayout.mHeadsUpBinderHandle = null
+ }
+
// set the content model after disposal and before setting new rich ongoing view
entry.setContentModel(result.contentModel)
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
@@ -1477,19 +1563,53 @@ constructor(
}
}
- // after updating the content model, set the view, then start the new binder
- result.richOngoingNotificationViewHolder?.let { viewHolder ->
- row.privateLayout.contractedChild = viewHolder.view
- row.privateLayout.expandedChild = null
- row.privateLayout.headsUpChild = null
- row.privateLayout.setExpandedInflatedSmartReplies(null)
- row.privateLayout.setHeadsUpInflatedSmartReplies(null)
- row.privateLayout.mContractedBinderHandle =
- viewHolder.binder.setupContentViewBinder()
- row.setExpandable(false)
+ val hasRichOngoingViewHolder =
+ result.contractedRichOngoingNotificationViewHolder != null ||
+ result.expandedRichOngoingNotificationViewHolder != null ||
+ result.headsUpRichOngoingNotificationViewHolder != null
+
+ if (hasRichOngoingViewHolder) {
+ // after updating the content model, set the view, then start the new binder
+ result.contractedRichOngoingNotificationViewHolder?.let { contractedViewHolder ->
+ if (contractedViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.contractedChild = contractedViewHolder.view
+ row.privateLayout.mContractedBinderHandle =
+ contractedViewHolder.binder.setupContentViewBinder()
+ } else if (contractedViewHolder == NullContentView) {
+ row.privateLayout.contractedChild = null
+ }
+ }
+
+ result.expandedRichOngoingNotificationViewHolder?.let { expandedViewHolder ->
+ if (expandedViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.expandedChild = expandedViewHolder.view
+ row.privateLayout.mExpandedBinderHandle =
+ expandedViewHolder.binder.setupContentViewBinder()
+ } else if (expandedViewHolder == NullContentView) {
+ row.privateLayout.expandedChild = null
+ }
+ }
+
+ result.headsUpRichOngoingNotificationViewHolder?.let { headsUpViewHolder ->
+ if (headsUpViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.headsUpChild = headsUpViewHolder.view
+ row.privateLayout.mHeadsUpBinderHandle =
+ headsUpViewHolder.binder.setupContentViewBinder()
+ } else if (headsUpViewHolder == NullContentView) {
+ row.privateLayout.headsUpChild = null
+ }
+ }
+
+ // clean remoteViewCache when we don't keep existing views.
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+
+ // Since RONs don't support smart reply, remove them from HUNs and Expanded.
+ row.privateLayout.setExpandedInflatedSmartReplies(null)
+ row.privateLayout.setHeadsUpInflatedSmartReplies(null)
+
+ row.setExpandable(row.privateLayout.expandedChild != null)
}
Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index bf5b3a34afb6..da29b0fd0dc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -22,6 +22,7 @@ import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
import com.android.systemui.statusbar.notification.row.shared.IconModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
@@ -68,12 +69,13 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
builder: Notification.Builder,
systemUIContext: Context,
packageContext: Context
- ): RichOngoingContentModel? =
+ ): RichOngoingContentModel? {
+ val sbn = entry.sbn
+ val notification = sbn.notification
+ val icon = IconModel(notification.smallIcon)
+
try {
- val sbn = entry.sbn
- val notification = sbn.notification
- val icon = IconModel(notification.smallIcon)
- if (sbn.packageName == "com.google.android.deskclock") {
+ return if (sbn.packageName == "com.google.android.deskclock") {
when (notification.channelId) {
"Timers v2" -> {
parseTimerNotification(notification, icon)
@@ -87,11 +89,14 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
null
}
}
+ } else if (builder.style is Notification.EnRouteStyle) {
+ parseEnRouteNotification(notification, icon)
} else null
} catch (e: Exception) {
Log.e("RONs", "Error parsing RON", e)
- null
+ return null
}
+ }
/**
* FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
@@ -199,4 +204,15 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
.plusMinutes(minute.toLong())
.plusSeconds(second.toLong())
}
+
+ private fun parseEnRouteNotification(
+ notification: Notification,
+ icon: IconModel,
+ ): EnRouteContentModel {
+ return EnRouteContentModel(
+ smallIcon = icon,
+ title = notification.extras.getCharSequence(Notification.EXTRA_TITLE),
+ text = notification.extras.getCharSequence(Notification.EXTRA_TEXT),
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
index e9c4960a4011..2c462b7329b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
@@ -24,12 +24,18 @@ import android.view.ViewGroup
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.StopwatchContentModel
import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
+import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
import com.android.systemui.statusbar.notification.row.ui.view.TimerView
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.EnRouteViewBinder
import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent
import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
import javax.inject.Inject
@@ -39,7 +45,35 @@ fun interface DeferredContentViewBinder {
fun setupContentViewBinder(): DisposableHandle
}
-class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder)
+enum class RichOngoingNotificationViewType {
+ Contracted,
+ Expanded,
+ HeadsUp,
+}
+
+/**
+ * * Supertype of the 3 different possible result types of
+ * [RichOngoingNotificationViewInflater.inflateView].
+ */
+sealed interface ContentViewInflationResult {
+
+ /** Indicates that the content view should be removed if present. */
+ data object NullContentView : ContentViewInflationResult
+
+ /**
+ * Indicates that the content view (which *must be* present) should be unmodified during this
+ * inflation.
+ */
+ data object KeepExistingView : ContentViewInflationResult
+
+ /**
+ * Contains the new view and binder that should replace any existing content view for this slot.
+ */
+ data class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder) :
+ ContentViewInflationResult
+}
+
+fun ContentViewInflationResult?.shouldDisposeViewBinder() = this !is KeepExistingView
/**
* Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
@@ -52,7 +86,14 @@ interface RichOngoingNotificationViewInflater {
entry: NotificationEntry,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder?
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult
+
+ fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean
}
@SysUISingleton
@@ -68,8 +109,9 @@ constructor(
entry: NotificationEntry,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder? {
- if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return null
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult {
+ if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return NullContentView
val component = viewModelComponentFactory.create(entry)
return when (contentModel) {
is TimerContentModel ->
@@ -77,9 +119,31 @@ constructor(
existingView,
component::createTimerViewModel,
systemUiContext,
- parentView
+ parentView,
+ viewType
)
- is StopwatchContentModel -> TODO("Not yet implemented")
+ is EnRouteContentModel ->
+ inflateEnRouteView(
+ existingView,
+ component::createEnRouteViewModel,
+ systemUiContext,
+ parentView,
+ viewType
+ )
+ else -> TODO("Not yet implemented")
+ }
+ }
+
+ override fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean {
+ if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
+ return when (contentModel) {
+ is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
+ is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType)
+ else -> TODO("Not yet implemented")
}
}
@@ -88,17 +152,65 @@ constructor(
createViewModel: () -> TimerViewModel,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder? {
- if (existingView is TimerView && !existingView.isReinflateNeeded()) return null
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.rich_ongoing_timer_notification,
- parentView,
- /* attachToRoot= */ false
- ) as TimerView
- return InflatedContentViewHolder(newView) {
- TimerViewBinder.bindWhileAttached(newView, createViewModel())
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult {
+ if (existingView is TimerView && !existingView.isReinflateNeeded()) return KeepExistingView
+
+ return when (viewType) {
+ RichOngoingNotificationViewType.Contracted -> {
+ val newView =
+ LayoutInflater.from(systemUiContext)
+ .inflate(
+ R.layout.rich_ongoing_timer_notification,
+ parentView,
+ /* attachToRoot= */ false
+ ) as TimerView
+ InflatedContentViewHolder(newView) {
+ TimerViewBinder.bindWhileAttached(newView, createViewModel())
+ }
+ }
+ RichOngoingNotificationViewType.Expanded,
+ RichOngoingNotificationViewType.HeadsUp -> NullContentView
+ }
+ }
+
+ private fun canKeepTimerView(
+ contentModel: TimerContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean = true
+
+ private fun inflateEnRouteView(
+ existingView: View?,
+ createViewModel: () -> EnRouteViewModel,
+ systemUiContext: Context,
+ parentView: ViewGroup,
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult {
+ if (existingView is EnRouteView && !existingView.isReinflateNeeded())
+ return KeepExistingView
+ return when (viewType) {
+ RichOngoingNotificationViewType.Contracted -> {
+ val newView =
+ LayoutInflater.from(systemUiContext)
+ .inflate(
+ R.layout.notification_template_en_route_contracted,
+ parentView,
+ /* attachToRoot= */ false
+ ) as EnRouteView
+
+ InflatedContentViewHolder(newView) {
+ EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
+ }
+ }
+ RichOngoingNotificationViewType.Expanded,
+ RichOngoingNotificationViewType.HeadsUp -> NullContentView
}
}
+
+ private fun canKeepEnRouteView(
+ contentModel: EnRouteContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
index 4705ace7fac6..72823a760197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row.domain.interactor
import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -26,4 +27,8 @@ class NotificationRowInteractor @Inject constructor(repository: NotificationRowR
/** Content of a rich ongoing timer notification. */
val timerContentModel: Flow<TimerContentModel> =
repository.richOngoingContentModel.filterIsInstance<TimerContentModel>()
+
+ /** Content of a rich ongoing timer notification. */
+ val enRouteContentModel: Flow<EnRouteContentModel> =
+ repository.richOngoingContentModel.filterIsInstance<EnRouteContentModel>()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
new file mode 100644
index 000000000000..7e78cca028ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+/**
+ * Represents something en route.
+ *
+ * @param smallIcon the main small icon of the EnRoute notification.
+ * @param title the title of the EnRoute notification.
+ * @param text the text of the EnRoute notification.
+ */
+data class EnRouteContentModel(
+ val smallIcon: IconModel,
+ val title: CharSequence?,
+ val text: CharSequence?,
+) : RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
new file mode 100644
index 000000000000..e5c2b5fff5e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.view
+
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.internal.R
+import com.android.internal.widget.NotificationExpandButton
+
+class EnRouteView
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+ private val configTracker = ConfigurationTracker(resources)
+
+ private lateinit var icon: ImageView
+ private lateinit var title: TextView
+ private lateinit var text: TextView
+ private lateinit var expandButton: NotificationExpandButton
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ icon = requireViewById(R.id.icon)
+ title = requireViewById(R.id.title)
+ text = requireViewById(R.id.text)
+
+ expandButton = requireViewById(R.id.expand_button)
+ expandButton.setExpanded(false)
+ }
+
+ /** the resources configuration has changed such that the view needs to be reinflated */
+ fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
+
+ fun setIcon(icon: Icon?) {
+ this.icon.setImageIcon(icon)
+ }
+
+ fun setTitle(title: CharSequence?) {
+ this.title.text = title
+ }
+
+ fun setText(text: CharSequence?) {
+ this.text.text = text
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
new file mode 100644
index 000000000000..3b8957c092f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [EnRouteView] to its [view model][EnRouteViewModel]. */
+object EnRouteViewBinder {
+ fun bindWhileAttached(
+ view: EnRouteView,
+ viewModel: EnRouteViewModel,
+ ): DisposableHandle {
+ return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
+ }
+
+ suspend fun bind(
+ view: EnRouteView,
+ viewModel: EnRouteViewModel,
+ ) = coroutineScope {
+ launch { viewModel.icon.collect { view.setIcon(it) } }
+ launch { viewModel.title.collect { view.setTitle(it) } }
+ launch { viewModel.text.collect { view.setText(it) } }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
new file mode 100644
index 000000000000..307a9834ccc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.graphics.drawable.Icon
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
+import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+
+/** A view model for EnRoute notifications. */
+class EnRouteViewModel
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ rowInteractor: NotificationRowInteractor,
+) : FlowDumperImpl(dumpManager) {
+ init {
+ /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
+ }
+
+ val icon: Flow<Icon?> = rowInteractor.enRouteContentModel.mapNotNull { it.smallIcon.icon }
+
+ val title: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.title }
+
+ val text: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.text }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
index dad52a3b2c45..5552d89b6f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
@@ -33,4 +33,6 @@ interface RichOngoingViewModelComponent {
}
fun createTimerViewModel(): TimerViewModel
+
+ fun createEnRouteViewModel(): EnRouteViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 6a2c60276186..a16129b076f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -511,10 +511,12 @@ public class AmbientState implements Dumpable {
}
public int getTopPadding() {
+ SceneContainerFlag.assertInLegacyMode();
return mTopPadding;
}
public void setTopPadding(int topPadding) {
+ SceneContainerFlag.assertInLegacyMode();
mTopPadding = topPadding;
}
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 f26f8405e299..d0c51bc28126 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
@@ -851,7 +851,7 @@ public class NotificationStackScrollLayout
return; // the rest of the fields are not important in Flexiglass
}
- y = getTopPadding();
+ y = mAmbientState.getTopPadding();
drawDebugInfo(canvas, y, Color.RED, /* label= */ "getTopPadding() = " + y);
y = getLayoutHeight();
@@ -1231,9 +1231,11 @@ public class NotificationStackScrollLayout
@Override
public void setStackTop(float stackTop) {
- mAmbientState.setStackTop(stackTop);
- // TODO(b/332574413): replace the following with using stackTop
- updateTopPadding(stackTop, isAddOrRemoveAnimationPending());
+ if (mAmbientState.getStackTop() != stackTop) {
+ mAmbientState.setStackTop(stackTop);
+ onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
+ setExpandedHeight(mExpandedHeight);
+ }
}
@Override
@@ -1386,28 +1388,30 @@ public class NotificationStackScrollLayout
}
public int getTopPadding() {
- return mAmbientState.getTopPadding();
+ // TODO(b/332574413) replace all usages of getTopPadding()
+ if (SceneContainerFlag.isEnabled()) {
+ return (int) mAmbientState.getStackTop();
+ } else {
+ return mAmbientState.getTopPadding();
+ }
}
- private void setTopPadding(int topPadding, boolean animate) {
- if (getTopPadding() != topPadding) {
- mAmbientState.setTopPadding(topPadding);
- boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
- updateAlgorithmHeightAndPadding();
- updateContentHeight();
- if (mAmbientState.isOnKeyguard()
- && !mShouldUseSplitNotificationShade
- && mShouldSkipTopPaddingAnimationAfterFold) {
- mShouldSkipTopPaddingAnimationAfterFold = false;
- } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
- mTopPaddingNeedsAnimation = true;
- mNeedsAnimation = true;
- }
- updateStackPosition();
- requestChildrenUpdate();
- notifyHeightChangeListener(null, shouldAnimate);
- mAnimateNextTopPaddingChange = false;
+ private void onTopPaddingChanged(boolean animate) {
+ boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
+ updateAlgorithmHeightAndPadding();
+ updateContentHeight();
+ if (mAmbientState.isOnKeyguard()
+ && !mShouldUseSplitNotificationShade
+ && mShouldSkipTopPaddingAnimationAfterFold) {
+ mShouldSkipTopPaddingAnimationAfterFold = false;
+ } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
+ mTopPaddingNeedsAnimation = true;
+ mNeedsAnimation = true;
}
+ updateStackPosition();
+ requestChildrenUpdate();
+ notifyHeightChangeListener(null, shouldAnimate);
+ mAnimateNextTopPaddingChange = false;
}
/**
@@ -1435,6 +1439,11 @@ public class NotificationStackScrollLayout
* @param listenerNeedsAnimation does the listener need to animate?
*/
private void updateStackPosition(boolean listenerNeedsAnimation) {
+ // When scene container is active, we only want to recalculate stack heights.
+ if (SceneContainerFlag.isEnabled()) {
+ updateStackEndHeightAndStackHeight(mAmbientState.getExpansionFraction());
+ return;
+ }
float topOverscrollAmount = mShouldUseSplitNotificationShade
? getCurrentOverScrollAmount(true /* top */) : 0f;
final float endTopPosition = getTopPadding() + mExtraTopInsetForFullShadeTransition
@@ -1447,10 +1456,8 @@ public class NotificationStackScrollLayout
if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
- if (!SceneContainerFlag.isEnabled()) {
- final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
- mAmbientState.setStackY(stackY);
- }
+ final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
+ mAmbientState.setStackY(stackY);
if (mOnStackYChanged != null) {
mOnStackYChanged.accept(listenerNeedsAnimation);
@@ -2708,6 +2715,7 @@ public class NotificationStackScrollLayout
* @param animate whether to animate the change
*/
public void updateTopPadding(float qsHeight, boolean animate) {
+ SceneContainerFlag.assertInLegacyMode();
int topPadding = (int) qsHeight;
int minStackHeight = getLayoutMinHeightInternal();
if (topPadding + minStackHeight > getHeight()) {
@@ -2715,7 +2723,10 @@ public class NotificationStackScrollLayout
} else {
mTopPaddingOverflow = 0;
}
- setTopPadding(topPadding, animate && !mKeyguardBypassEnabled);
+ if (mAmbientState.getTopPadding() != topPadding) {
+ mAmbientState.setTopPadding(topPadding);
+ onTopPaddingChanged(/* animate = */ animate && !mKeyguardBypassEnabled);
+ }
setExpandedHeight(mExpandedHeight);
}
@@ -4829,6 +4840,7 @@ public class NotificationStackScrollLayout
}
public boolean isBelowLastNotification(float touchX, float touchY) {
+ SceneContainerFlag.assertInLegacyMode();
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
ExpandableView child = getChildAtIndex(i);
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 55f05662f0c6..c25b30db7754 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
@@ -1235,6 +1235,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public boolean isBelowLastNotification(float x, float y) {
+ SceneContainerFlag.assertInLegacyMode();
return mView.isBelowLastNotification(x, y);
}
@@ -1328,6 +1329,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public int getTopPadding() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getTopPadding();
}
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 288924dd4000..1289cec3a282 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
@@ -86,6 +86,9 @@ interface NotificationScrollView {
/** Sets whether the view is displayed in doze mode. */
fun setDozing(dozing: Boolean)
+ /** Sets whether the view is displayed in pulsing mode. */
+ fun setPulsing(pulsing: Boolean, animated: Boolean)
+
/** Gets the inset for HUNs when they are not visible */
fun getHeadsUpInset(): Int
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 40761e07e33c..c044f6f6a9b1 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
@@ -89,6 +89,11 @@ constructor(
launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
launch {
+ viewModel.isPulsing.collect { isPulsing ->
+ view.setPulsing(isPulsing, viewModel.shouldAnimatePulse.value)
+ }
+ }
+ launch {
viewModel.shouldResetStackTop
.filter { it }
.collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) }
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 6489264fc2a7..b2045fe7569a 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
@@ -17,10 +17,13 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.compose.animation.scene.ObservableTransitionState.Transition
+import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeCurrentScene
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -39,6 +42,8 @@ import dagger.Lazy
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
@@ -58,42 +63,47 @@ constructor(
keyguardInteractor: Lazy<KeyguardInteractor>,
) :
ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
- SysUiViewModel() {
+ SysUiViewModel,
+ ExclusiveActivatable() {
override suspend fun onActivated(): Nothing {
activateFlowDumper()
}
- private fun expandFractionForScene(scene: SceneKey, shadeExpansion: Float): Float =
- when (scene) {
+ private fun expandedInScene(scene: SceneKey): Boolean {
+ return when (scene) {
Scenes.Lockscreen,
- Scenes.QuickSettings -> 1f
- else -> shadeExpansion
+ Scenes.Shade,
+ Scenes.QuickSettings -> true
+ else -> false
}
+ }
+
+ private fun fullyExpandedDuringSceneChange(change: ChangeCurrentScene): Boolean {
+ // The lockscreen stack is visible during all transitions away from the lockscreen, so keep
+ // the stack expanded until those transitions finish.
+ return (expandedInScene(change.fromScene) && expandedInScene(change.toScene)) ||
+ change.isBetween({ it == Scenes.Lockscreen }, { true })
+ }
- private fun expandFractionForTransition(
- state: ObservableTransitionState.Transition,
+ private fun expandFractionDuringSceneChange(
+ change: ChangeCurrentScene,
shadeExpansion: Float,
- shadeMode: ShadeMode,
qsExpansion: Float,
- quickSettingsScene: SceneKey
- ): Float =
- if (
- state.isBetween({ it == Scenes.Lockscreen }, { it in SceneFamilies.NotifShade }) ||
- state.isBetween({ it in SceneFamilies.NotifShade }, { it == quickSettingsScene })
- ) {
+ ): Float {
+ return if (fullyExpandedDuringSceneChange(change)) {
1f
- } else if (
- shadeMode != ShadeMode.Split &&
- state.isBetween({ it in SceneFamilies.Home }, { it == quickSettingsScene })
- ) {
+ } else if (change.isBetween({ it == Scenes.Gone }, { it in SceneFamilies.NotifShade })) {
+ shadeExpansion
+ } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
// during QS expansion, increase fraction at same rate as scrim alpha,
// but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
(qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA - EXPANSION_FOR_DELAYED_STACK_FADE_IN)
.coerceIn(0f, 1f)
} else {
- shadeExpansion
+ 0f
}
+ }
/**
* The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
@@ -107,18 +117,17 @@ constructor(
shadeInteractor.qsExpansion,
sceneInteractor.transitionState,
sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
- ) { shadeExpansion, shadeMode, qsExpansion, transitionState, quickSettingsScene ->
+ ) { shadeExpansion, _, qsExpansion, transitionState, _ ->
when (transitionState) {
- is ObservableTransitionState.Idle ->
- expandFractionForScene(transitionState.currentScene, shadeExpansion)
- is ObservableTransitionState.Transition ->
- expandFractionForTransition(
+ is Idle -> if (expandedInScene(transitionState.currentScene)) 1f else 0f
+ is ChangeCurrentScene ->
+ expandFractionDuringSceneChange(
transitionState,
shadeExpansion,
- shadeMode,
qsExpansion,
- quickSettingsScene
)
+ is Transition.ShowOrHideOverlay,
+ is Transition.ReplaceOverlay -> TODO("b/359173565: Handle overlay transitions")
}
}
.distinctUntilChanged()
@@ -129,9 +138,7 @@ constructor(
val shouldResetStackTop: Flow<Boolean> =
sceneInteractor.transitionState
- .mapNotNull { state ->
- state is ObservableTransitionState.Idle && state.currentScene == Scenes.Gone
- }
+ .mapNotNull { state -> state is Idle && state.currentScene == Scenes.Gone }
.distinctUntilChanged()
.dumpWhileCollecting("shouldResetStackTop")
@@ -213,13 +220,30 @@ constructor(
}
}
+ /** Whether the notification stack is displayed in pulsing mode. */
+ val isPulsing: Flow<Boolean> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ keyguardInteractor.get().isPulsing.dumpWhileCollecting("isPulsing")
+ }
+ }
+
+ val shouldAnimatePulse: StateFlow<Boolean> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ MutableStateFlow(false)
+ } else {
+ keyguardInteractor.get().isAodAvailable
+ }
+ }
+
@AssistedFactory
interface Factory {
fun create(): NotificationScrollViewModel
}
}
-private fun ObservableTransitionState.Transition.isBetween(
+private fun ChangeCurrentScene.isBetween(
a: (SceneKey) -> Boolean,
b: (SceneKey) -> Boolean
): Boolean = (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index ffa1de79b7e4..d891f62b4563 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -20,6 +20,7 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -52,7 +53,8 @@ constructor(
featureFlags: FeatureFlagsClassic,
dumpManager: DumpManager,
) :
- SysUiViewModel(),
+ SysUiViewModel,
+ ExclusiveActivatable(),
ActivatableFlowDumper by ActivatableFlowDumperImpl(
dumpManager = dumpManager,
tag = "NotificationsPlaceholderViewModel",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3dd265bb92da..e3242d13077f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2365,9 +2365,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// lock screen where users can use the UDFPS affordance to enter the device
mStatusBarKeyguardViewManager.reset(true);
} else if (mState == StatusBarState.KEYGUARD
- && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
- && mStatusBarKeyguardViewManager.isSecure()) {
- if (!relockWithPowerButtonImmediately()) {
+ && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
+ boolean needsBouncer = mStatusBarKeyguardViewManager.isSecure();
+ if (relockWithPowerButtonImmediately()) {
+ // Only request if SIM bouncer is needed
+ needsBouncer = mStatusBarKeyguardViewManager.needsFullscreenBouncer();
+ }
+
+ if (needsBouncer) {
Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
if (SceneContainerFlag.isEnabled()) {
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 00673169d036..f64941842a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -25,6 +25,7 @@ import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import javax.inject.Inject;
@@ -124,6 +125,11 @@ public class DozeScrimController implements StateListener {
// Begin pulse. Note that it's very important that the pulse finished callback
// be invoked when we're done so that the caller can drop the pulse wakelock.
+ if (SceneContainerFlag.isEnabled()) {
+ // ScrimController.Callback#onDisplayBlanked is no longer triggered when flexiglass is
+ // on, but we still need to signal that pulsing has started.
+ callback.onPulseStarted();
+ }
mPulseCallback = callback;
mPulseReason = reason;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 3ba62b16a748..d1b5160fd490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -44,6 +44,8 @@ import android.view.View;
import androidx.lifecycle.Observer;
+import com.android.settingslib.notification.modes.ZenIconLoader;
+import com.android.settingslib.notification.modes.ZenMode;
import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -78,6 +80,7 @@ import com.android.systemui.statusbar.policy.RotationLockController.RotationLock
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.DateFormatUtil;
@@ -99,7 +102,6 @@ public class PhoneStatusBarPolicy
CommandQueue.Callbacks,
RotationLockControllerCallback,
Listener,
- ZenModeController.Callback,
DeviceProvisionedListener,
KeyguardStateController.Callback,
PrivacyItemController.Callback,
@@ -161,6 +163,7 @@ public class PhoneStatusBarPolicy
private final RecordingController mRecordingController;
private final RingerModeTracker mRingerModeTracker;
private final PrivacyLogger mPrivacyLogger;
+ private final ZenModeInteractor mZenModeInteractor;
private boolean mZenVisible;
private boolean mVibrateVisible;
@@ -193,6 +196,7 @@ public class PhoneStatusBarPolicy
PrivacyItemController privacyItemController,
PrivacyLogger privacyLogger,
ConnectedDisplayInteractor connectedDisplayInteractor,
+ ZenModeInteractor zenModeInteractor,
JavaAdapter javaAdapter
) {
mIconController = iconController;
@@ -224,6 +228,7 @@ public class PhoneStatusBarPolicy
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
mPrivacyLogger = privacyLogger;
+ mZenModeInteractor = zenModeInteractor;
mJavaAdapter = javaAdapter;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
@@ -355,7 +360,13 @@ public class PhoneStatusBarPolicy
mBluetooth.addCallback(this);
mProvisionedController.addCallback(this);
mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
- mZenController.addCallback(this);
+ if (usesModeIcons()) {
+ // Note that we're not fully replacing ZenModeController with ZenModeInteractor, so
+ // we listen for the extra event here but still add the ZMC callback.
+ mJavaAdapter.alwaysCollectFlow(mZenModeInteractor.getMainActiveMode(),
+ this::onActiveModeChanged);
+ }
+ mZenController.addCallback(mZenControllerCallback);
if (!Flags.statusBarScreenSharingChips()) {
// If the flag is enabled, the cast icon is handled in the new screen sharing chips
// instead of here so we don't need to listen for events here.
@@ -385,16 +396,45 @@ public class PhoneStatusBarPolicy
() -> mResources.getString(R.string.accessibility_managed_profile));
}
- @Override
- public void onZenChanged(int zen) {
- updateVolumeZen();
- }
+ private void onActiveModeChanged(@Nullable ZenMode mode) {
+ if (!usesModeIcons()) {
+ Log.wtf(TAG, "onActiveModeChanged shouldn't be called if MODES_UI_ICONS is disabled");
+ return;
+ }
+ boolean visible = mode != null;
+ if (visible) {
+ // TODO: b/360399800 - Get the resource id, package, and cached drawable from the mode;
+ // this is a shortcut for testing (there should be no direct dependency on
+ // ZenIconLoader here).
+ String resPackage = mode.isSystemOwned() ? null : mode.getRule().getPackageName();
+ int iconResId = mode.getRule().getIconResId();
+ if (iconResId == 0) {
+ iconResId = ZenIconLoader.getIconResourceIdFromType(mode.getType());
+ }
- @Override
- public void onConsolidatedPolicyChanged(NotificationManager.Policy policy) {
- updateVolumeZen();
+ mIconController.setResourceIcon(mSlotZen, resPackage, iconResId,
+ /* preloadedIcon= */ null, mode.getName());
+ }
+ if (visible != mZenVisible) {
+ mIconController.setIconVisibility(mSlotZen, visible);
+ mZenVisible = visible;
+ }
}
+ // TODO: b/308591859 - Should be removed and use the ZenModeInteractor only.
+ private final ZenModeController.Callback mZenControllerCallback =
+ new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ updateVolumeZen();
+ }
+
+ @Override
+ public void onConsolidatedPolicyChanged(NotificationManager.Policy policy) {
+ updateVolumeZen();
+ }
+ };
+
private void updateAlarm() {
final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
@@ -417,15 +457,24 @@ public class PhoneStatusBarPolicy
return mResources.getString(R.string.accessibility_quick_settings_alarm, dateString);
}
- private final void updateVolumeZen() {
+ private void updateVolumeZen() {
+ int zen = mZenController.getZen();
+ if (!usesModeIcons()) {
+ updateZenIcon(zen);
+ }
+ updateRingerAndAlarmIcons(zen);
+ }
+
+ private void updateZenIcon(int zen) {
+ if (usesModeIcons()) {
+ Log.wtf(TAG, "updateZenIcon shouldn't be called if MODES_UI_ICONS is enabled");
+ return;
+ }
+
boolean zenVisible = false;
int zenIconId = 0;
String zenDescription = null;
- boolean vibrateVisible = false;
- boolean muteVisible = false;
- int zen = mZenController.getZen();
-
if (DndTile.isVisible(mSharedPreferences) || DndTile.isCombinedIcon(mSharedPreferences)) {
zenVisible = zen != Global.ZEN_MODE_OFF;
zenIconId = R.drawable.stat_sys_dnd;
@@ -440,7 +489,21 @@ public class PhoneStatusBarPolicy
zenDescription = mResources.getString(R.string.interruption_level_priority);
}
- if (!ZenModeConfig.isZenOverridingRinger(zen, mZenController.getConsolidatedPolicy())) {
+ if (zenVisible) {
+ mIconController.setIcon(mSlotZen, zenIconId, zenDescription);
+ }
+ if (zenVisible != mZenVisible) {
+ mIconController.setIconVisibility(mSlotZen, zenVisible);
+ mZenVisible = zenVisible;
+ }
+ }
+
+ private void updateRingerAndAlarmIcons(int zen) {
+ boolean vibrateVisible = false;
+ boolean muteVisible = false;
+
+ NotificationManager.Policy consolidatedPolicy = mZenController.getConsolidatedPolicy();
+ if (!ZenModeConfig.isZenOverridingRinger(zen, consolidatedPolicy)) {
final Integer ringerModeInternal =
mRingerModeTracker.getRingerModeInternal().getValue();
if (ringerModeInternal != null) {
@@ -452,14 +515,6 @@ public class PhoneStatusBarPolicy
}
}
- if (zenVisible) {
- mIconController.setIcon(mSlotZen, zenIconId, zenDescription);
- }
- if (zenVisible != mZenVisible) {
- mIconController.setIconVisibility(mSlotZen, zenVisible);
- mZenVisible = zenVisible;
- }
-
if (vibrateVisible != mVibrateVisible) {
mIconController.setIconVisibility(mSlotVibrate, vibrateVisible);
mVibrateVisible = vibrateVisible;
@@ -888,4 +943,9 @@ public class PhoneStatusBarPolicy
mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
}
+
+ private static boolean usesModeIcons() {
+ return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+ && android.app.Flags.modesUiIcons();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0f93ff2b70ed..f11fd7b29c18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -68,7 +68,6 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
@@ -104,6 +103,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import dagger.Lazy;
@@ -179,6 +179,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private float mFraction = -1f;
private boolean mTracking = false;
private boolean mBouncerShowingOverDream;
+ private int mAttemptsToShowBouncer = 0;
+ private DelayableExecutor mExecutor;
private final PrimaryBouncerExpansionCallback mExpansionCallback =
new PrimaryBouncerExpansionCallback() {
@@ -315,8 +317,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
- private FeatureFlags mFlags;
-
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsBackAnimationEnabled;
private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@@ -399,9 +399,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
JavaAdapter javaAdapter,
Lazy<SceneInteractor> sceneInteractorLazy,
StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor,
+ @Main DelayableExecutor executor,
Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy
) {
mContext = context;
+ mExecutor = executor;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
mConfigurationController = configurationController;
@@ -711,13 +713,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* {@link #needsFullscreenBouncer()}.
*/
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
- boolean isDozing = mDozing;
- if (Flags.simPinRaceConditionOnRestart()) {
- KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
- .getTo();
- isDozing = mDozing || toState == KeyguardState.DOZING || toState == KeyguardState.AOD;
- }
- if (needsFullscreenBouncer() && !isDozing) {
+ if (needsFullscreenBouncer() && !mDozing) {
// The keyguard might be showing (already). So we need to hide it.
if (!primaryBouncerIsShowing()) {
if (SceneContainerFlag.isEnabled()) {
@@ -727,9 +723,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
} else {
if (Flags.simPinRaceConditionOnRestart()) {
if (mPrimaryBouncerInteractor.show(/* isScrimmed= */ true)) {
+ mAttemptsToShowBouncer = 0;
mCentralSurfaces.hideKeyguard();
} else {
- mCentralSurfaces.showKeyguard();
+ if (mAttemptsToShowBouncer > 6) {
+ mAttemptsToShowBouncer = 0;
+ Log.e(TAG, "Too many failed attempts to show bouncer, showing "
+ + "keyguard instead");
+ mCentralSurfaces.showKeyguard();
+ } else {
+ Log.v(TAG, "Failed to show bouncer, attempt #: "
+ + mAttemptsToShowBouncer++);
+ mExecutor.executeDelayed(() ->
+ showBouncerOrKeyguard(hideBouncerWhenShowing,
+ isFalsingReset),
+ 500);
+ }
}
} else {
mCentralSurfaces.hideKeyguard();
@@ -1874,6 +1883,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
|| mode == KeyguardSecurityModel.SecurityMode.SimPuk;
}
+ @VisibleForTesting
+ void setAttemptsToShowBouncer(int attempts) {
+ mAttemptsToShowBouncer = attempts;
+ }
+
/**
* Delegate used to send show and hide events to an alternate authentication method instead of
* the regular pin/pattern/password bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
index 1ada30e3518a..ee528e915079 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
@@ -18,9 +18,12 @@ package com.android.systemui.statusbar.phone.ui;
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.ArraySet;
+import androidx.annotation.DrawableRes;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
@@ -58,6 +61,18 @@ public interface StatusBarIconController {
void setIcon(String slot, int resourceId, CharSequence contentDescription);
/**
+ * Adds or updates an icon for the given slot.
+ *
+ * @param resPackage the package name containing the resource in question. Can be null if the
+ * icon is a system icon (e.g. a resource from {@code android.R.drawable} or
+ * {@code com.android.internal.R.drawable}).
+ * @param iconResId id of the drawable resource
+ * @param preloadedIcon optional drawable corresponding to {@code iconResId}, if known
+ */
+ void setResourceIcon(String slot, @Nullable String resPackage, @DrawableRes int iconResId,
+ @Nullable Drawable preloadedIcon, CharSequence contentDescription);
+
+ /**
* Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
* set up (inflated and added to the view hierarchy).
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
index 85213cb0ebff..ad3a9e350c4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
@@ -18,16 +18,22 @@ package com.android.systemui.statusbar.phone.ui;
import static com.android.systemui.statusbar.phone.ui.StatusBarIconList.Slot;
+import static com.google.common.base.Preconditions.checkArgument;
+
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.view.ViewGroup;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
@@ -221,19 +227,66 @@ public class StatusBarIconControllerImpl implements Tunable,
}
}
- /** */
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
+ setResourceIconInternal(
+ slot,
+ Icon.createWithResource(mContext, resourceId),
+ /* preloadedIcon= */ null,
+ contentDescription,
+ StatusBarIcon.Type.SystemIcon);
+ }
+
+ @Override
+ public void setResourceIcon(String slot, @Nullable String resPackage,
+ @DrawableRes int iconResId, @Nullable Drawable preloadedIcon,
+ CharSequence contentDescription) {
+ if (!usesModeIcons()) {
+ Log.wtf("TAG",
+ "StatusBarIconController.setResourceIcon() should not be called without "
+ + "MODES_UI & MODES_UI_ICONS!");
+ // Fall back to old implementation, although it will not load the icon if it's from a
+ // different package.
+ setIcon(slot, iconResId, contentDescription);
+ return;
+ }
+
+ Icon icon = resPackage != null
+ ? Icon.createWithResource(resPackage, iconResId)
+ : Icon.createWithResource(mContext, iconResId);
+
+ setResourceIconInternal(
+ slot,
+ icon,
+ preloadedIcon,
+ contentDescription,
+ StatusBarIcon.Type.ResourceIcon);
+ }
+
+ private void setResourceIconInternal(String slot, Icon resourceIcon,
+ @Nullable Drawable preloadedIcon, CharSequence contentDescription,
+ StatusBarIcon.Type type) {
+ checkArgument(resourceIcon.getType() == Icon.TYPE_RESOURCE,
+ "Expected Icon of TYPE_RESOURCE, but got " + resourceIcon.getType());
+ String resPackage = resourceIcon.getResPackage();
+ if (TextUtils.isEmpty(resPackage)) {
+ resPackage = mContext.getPackageName();
+ }
+
StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
if (holder == null) {
- StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
- Icon.createWithResource(mContext, resourceId), 0, 0,
- contentDescription, StatusBarIcon.Type.SystemIcon);
+ StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, resPackage,
+ resourceIcon, /* iconLevel= */ 0, /* number=*/ 0,
+ contentDescription, type);
+ icon.preloadedIcon = preloadedIcon;
holder = StatusBarIconHolder.fromIcon(icon);
setIcon(slot, holder);
} else {
- holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
+ holder.getIcon().pkg = resPackage;
+ holder.getIcon().icon = resourceIcon;
holder.getIcon().contentDescription = contentDescription;
+ holder.getIcon().type = type;
+ holder.getIcon().preloadedIcon = preloadedIcon;
handleSet(slot, holder);
}
}
@@ -524,4 +577,9 @@ public class StatusBarIconControllerImpl implements Tunable,
return slot + EXTERNAL_SLOT_SUFFIX;
}
}
+
+ private static boolean usesModeIcons() {
+ return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+ && android.app.Flags.modesUiIcons();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 199b5b672140..37f2f195ebf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -36,14 +36,12 @@ import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -76,37 +74,10 @@ constructor(
@DeviceBasedSatelliteInputLog logBuffer: LogBuffer,
@DeviceBasedSatelliteTableLog tableLog: TableLogBuffer,
) : DeviceBasedSatelliteViewModel {
- private val shouldShowIcon: Flow<Boolean> =
- interactor.areAllConnectionsOutOfService
- .flatMapLatest { allOos ->
- if (!allOos) {
- flowOf(false)
- } else {
- combine(
- interactor.isSatelliteAllowed,
- interactor.isSatelliteProvisioned,
- interactor.isWifiActive,
- airplaneModeRepository.isAirplaneMode
- ) { isSatelliteAllowed, isSatelliteProvisioned, isWifiActive, isAirplaneMode ->
- isSatelliteAllowed &&
- isSatelliteProvisioned &&
- !isWifiActive &&
- !isAirplaneMode
- }
- }
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "vm",
- columnName = COL_VISIBLE_CONDITION,
- initialValue = false,
- )
// This adds a 10 seconds delay before showing the icon
- private val shouldActuallyShowIcon: StateFlow<Boolean> =
- shouldShowIcon
- .distinctUntilChanged()
+ private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
+ interactor.areAllConnectionsOutOfService
.flatMapLatest { shouldShow ->
if (shouldShow) {
logBuffer.log(
@@ -125,6 +96,45 @@ constructor(
.logDiffsForTable(
tableLog,
columnPrefix = "vm",
+ columnName = COL_VISIBLE_FOR_OOS,
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private val canShowIcon =
+ combine(
+ interactor.isSatelliteAllowed,
+ interactor.isSatelliteProvisioned,
+ ) { allowed, provisioned ->
+ allowed && provisioned
+ }
+
+ private val showIcon =
+ canShowIcon
+ .flatMapLatest { canShow ->
+ if (!canShow) {
+ flowOf(false)
+ } else {
+ combine(
+ shouldShowIconForOosAfterHysteresis,
+ interactor.connectionState,
+ interactor.isWifiActive,
+ airplaneModeRepository.isAirplaneMode,
+ ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
+ if (isWifiActive || isAirplaneMode) {
+ false
+ } else {
+ showForOos ||
+ connectionState == SatelliteConnectionState.On ||
+ connectionState == SatelliteConnectionState.Connected
+ }
+ }
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
columnName = COL_VISIBLE,
initialValue = false,
)
@@ -132,7 +142,7 @@ constructor(
override val icon: StateFlow<Icon?> =
combine(
- shouldActuallyShowIcon,
+ showIcon,
interactor.connectionState,
interactor.signalStrength,
) { shouldShow, state, signalStrength ->
@@ -146,7 +156,7 @@ constructor(
override val carrierText: StateFlow<String?> =
combine(
- shouldActuallyShowIcon,
+ showIcon,
interactor.connectionState,
) { shouldShow, connectionState ->
logBuffer.log(
@@ -156,7 +166,7 @@ constructor(
bool1 = shouldShow
str1 = connectionState.name
},
- { "Updating carrier text. shouldActuallyShow=$bool1 connectionState=$str1" }
+ { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
)
if (shouldShow) {
when (connectionState) {
@@ -165,28 +175,30 @@ constructor(
context.getString(R.string.satellite_connected_carrier_text)
SatelliteConnectionState.Off,
SatelliteConnectionState.Unknown -> {
- null
+ // If we're showing the satellite icon opportunistically, use the
+ // emergency-only version of the carrier string
+ context.getString(R.string.satellite_emergency_only_carrier_text)
}
}
} else {
null
}
}
- .onEach {
- logBuffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = it },
- { "Resulting carrier text = $str1" }
- )
- }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_CARRIER_TEXT,
+ initialValue = null,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
companion object {
private const val TAG = "DeviceBasedSatelliteViewModel"
private val DELAY_DURATION = 10.seconds
- const val COL_VISIBLE_CONDITION = "visCondition"
+ const val COL_VISIBLE_FOR_OOS = "visibleForOos"
const val COL_VISIBLE = "visible"
+ const val COL_CARRIER_TEXT = "carrierText"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index f16fcb5d4e82..d351da68e1c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -95,10 +95,6 @@ constructor(
return mode.getIcon(context, iconLoader).await().asIcon()
}
- suspend fun getLockscreenModeIcon(mode: ZenMode): Icon {
- return mode.getLockscreenIcon(context, iconLoader).await().asIcon()
- }
-
/**
* Given the list of modes (which may include zero or more currently active modes), returns an
* icon representing the active mode, if any (or, if multiple modes are active, to the most
@@ -106,8 +102,8 @@ constructor(
* standard DND icon for implicit modes, instead of the launcher icon of the associated
* package).
*/
- suspend fun getActiveModeIcon(context: Context, modes: List<ZenMode>): Icon? {
- return getMainActiveMode(modes)?.let { m -> getLockscreenModeIcon(m) }
+ suspend fun getActiveModeIcon(modes: List<ZenMode>): Icon? {
+ return getMainActiveMode(modes)?.let { m -> getModeIcon(m) }
}
fun activateMode(zenMode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 1c8041ff5b31..a3b186744537 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -16,7 +16,6 @@
package com.android.systemui.touchpad.tutorial.ui.composable
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.airbnb.lottie.compose.rememberLottieDynamicProperties
@@ -67,7 +66,6 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
- val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
val dynamicProperties =
rememberLottieDynamicProperties(
rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
@@ -76,10 +74,9 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
)
val screenColors =
- remember(onTertiaryFixed, surfaceContainer, tertiaryFixedDim, dynamicProperties) {
+ remember(dynamicProperties) {
TutorialScreenConfig.Colors(
background = onTertiaryFixed,
- successBackground = surfaceContainer,
title = tertiaryFixedDim,
animationColors = dynamicProperties,
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 0a6283aa7417..d4eb0cd7327b 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -16,7 +16,6 @@
package com.android.systemui.touchpad.tutorial.ui.composable
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.airbnb.lottie.compose.rememberLottieDynamicProperties
@@ -66,7 +65,6 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
val primaryFixedDim = LocalAndroidColorScheme.current.primaryFixedDim
val onPrimaryFixed = LocalAndroidColorScheme.current.onPrimaryFixed
val onPrimaryFixedVariant = LocalAndroidColorScheme.current.onPrimaryFixedVariant
- val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
val dynamicProperties =
rememberLottieDynamicProperties(
rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
@@ -74,10 +72,9 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
)
val screenColors =
- remember(surfaceContainer, dynamicProperties) {
+ remember(dynamicProperties) {
TutorialScreenConfig.Colors(
background = onPrimaryFixed,
- successBackground = surfaceContainer,
title = primaryFixedDim,
animationColors = dynamicProperties,
)
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
index ae0061b02ace..727e51fdef11 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
@@ -19,7 +19,7 @@ package com.android.systemui.util.kotlin
import android.util.IndentingPrintWriter
import com.android.systemui.Dumpable
import com.android.systemui.dump.DumpManager
-import com.android.systemui.lifecycle.BaseActivatable
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
@@ -189,7 +189,7 @@ class ActivatableFlowDumperImpl(
) : SimpleFlowDumper(), ActivatableFlowDumper {
private val registration =
- object : BaseActivatable() {
+ object : ExclusiveActivatable() {
override suspend fun onActivated(): Nothing {
try {
dumpManager.registerCriticalDumpable(
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 28ac2c0e8283..055671cf32ca 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -28,6 +28,7 @@ import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -62,7 +63,9 @@ constructor(
/**
* Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
* [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
- * during onViewAttached() and removing during onViewRemoved()
+ * during onViewAttached() and removing during onViewRemoved().
+ *
+ * @return a disposable handle in order to cancel the flow in the future.
*/
@JvmOverloads
fun <T> collectFlow(
@@ -71,8 +74,8 @@ fun <T> collectFlow(
consumer: Consumer<T>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
state: Lifecycle.State = Lifecycle.State.CREATED,
-) {
- view.repeatWhenAttached(coroutineContext) {
+): DisposableHandle {
+ return view.repeatWhenAttached(coroutineContext) {
repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 50efc21f50fc..5f6ad9205ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -60,7 +60,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.onehanded.OneHanded;
@@ -70,6 +69,7 @@ import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java
new file mode 100644
index 000000000000..ba990efd5162
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Test for {@link AccessibilityGestureTargetsObserver}. */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class AccessibilityGestureTargetsObserverTest extends SysuiTestCase {
+ private static final int MY_USER_ID = ActivityManager.getCurrentUser();
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private UserTracker mUserTracker;
+ @Mock
+ private AccessibilityGestureTargetsObserver.TargetsChangedListener mListener;
+
+ private AccessibilityGestureTargetsObserver mAccessibilityGestureTargetsObserver;
+
+ private static final String TEST_A11Y_BTN_TARGETS = "Magnification";
+
+ @Before
+ public void setUp() {
+ when(mUserTracker.getUserId()).thenReturn(MY_USER_ID);
+ mAccessibilityGestureTargetsObserver = new AccessibilityGestureTargetsObserver(mContext,
+ mUserTracker);
+ }
+
+ @Test
+ public void onChange_haveListener_invokeCallback() {
+ mAccessibilityGestureTargetsObserver.addListener(mListener);
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS,
+ MY_USER_ID);
+
+ mAccessibilityGestureTargetsObserver.mContentObserver.onChange(false);
+
+ verify(mListener).onAccessibilityGestureTargetsChanged(TEST_A11Y_BTN_TARGETS);
+ }
+
+ @Test
+ public void onChange_listenerRemoved_noInvokeCallback() {
+ mAccessibilityGestureTargetsObserver.addListener(mListener);
+ mAccessibilityGestureTargetsObserver.removeListener(mListener);
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS,
+ MY_USER_ID);
+
+ mAccessibilityGestureTargetsObserver.mContentObserver.onChange(false);
+
+ verify(mListener, never()).onAccessibilityGestureTargetsChanged(anyString());
+ }
+
+ @Test
+ public void getCurrentAccessibilityGestureTargets_expectedValue() {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS,
+ MY_USER_ID);
+
+ final String actualValue =
+ mAccessibilityGestureTargetsObserver.getCurrentAccessibilityGestureTargets();
+
+ assertThat(actualValue).isEqualTo(TEST_A11Y_BTN_TARGETS);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 5600b87280ad..a18d272b8fe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -711,6 +711,16 @@ public class TouchMonitorTest extends SysuiTestCase {
}
@Test
+ public void testDestroy_cleansUpHandler() {
+ final TouchHandler touchHandler = createTouchHandler();
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+ environment.destroyMonitor();
+ verify(touchHandler).onDestroy();
+ }
+
+ @Test
public void testLastSessionPop_createsNewInputSession() {
final TouchHandler touchHandler = createTouchHandler();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index e60848bb6bc3..6e9b24f1433e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -123,19 +123,19 @@ class CommunalBackupHelperTest : SysuiTestCase() {
FakeWidgetMetadata(
widgetId = 11,
componentName = "com.android.fakePackage1/fakeWidget1",
- rank = 3,
+ rank = 0,
userSerialNumber = 0,
),
FakeWidgetMetadata(
widgetId = 12,
componentName = "com.android.fakePackage2/fakeWidget2",
- rank = 2,
+ rank = 1,
userSerialNumber = 0,
),
FakeWidgetMetadata(
widgetId = 13,
componentName = "com.android.fakePackage3/fakeWidget3",
- rank = 1,
+ rank = 2,
userSerialNumber = 10,
),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
index eb0ab782ae3f..ad2550255e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -72,6 +72,82 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
databaseV2.verifyWidgetsV2(fakeWidgetsV1.map { it.getV2() })
}
+ @Test
+ fun migrate2To3_noGapBetweenRanks_ranksReversed() {
+ // Create a communal database in version 2
+ val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)
+
+ // Populate some fake data
+ val fakeRanks =
+ listOf(
+ FakeCommunalItemRank(3),
+ FakeCommunalItemRank(2),
+ FakeCommunalItemRank(1),
+ FakeCommunalItemRank(0),
+ )
+ databaseV2.insertRanks(fakeRanks)
+
+ // Verify fake ranks populated
+ databaseV2.verifyRanksInOrder(fakeRanks)
+
+ // Run migration and get database V3
+ val databaseV3 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 3,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_2_3,
+ )
+
+ // Verify ranks are reversed
+ databaseV3.verifyRanksInOrder(
+ listOf(
+ FakeCommunalItemRank(0),
+ FakeCommunalItemRank(1),
+ FakeCommunalItemRank(2),
+ FakeCommunalItemRank(3),
+ )
+ )
+ }
+
+ @Test
+ fun migrate2To3_withGapBetweenRanks_ranksReversed() {
+ // Create a communal database in version 2
+ val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)
+
+ // Populate some fake data with gaps between ranks
+ val fakeRanks =
+ listOf(
+ FakeCommunalItemRank(9),
+ FakeCommunalItemRank(7),
+ FakeCommunalItemRank(2),
+ FakeCommunalItemRank(0),
+ )
+ databaseV2.insertRanks(fakeRanks)
+
+ // Verify fake ranks populated
+ databaseV2.verifyRanksInOrder(fakeRanks)
+
+ // Run migration and get database V3
+ val databaseV3 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 3,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_2_3,
+ )
+
+ // Verify ranks are reversed
+ databaseV3.verifyRanksInOrder(
+ listOf(
+ FakeCommunalItemRank(0),
+ FakeCommunalItemRank(2),
+ FakeCommunalItemRank(7),
+ FakeCommunalItemRank(9),
+ )
+ )
+ }
+
private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
widgets.forEach { widget ->
execSQL(
@@ -117,6 +193,25 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
assertThat(cursor.isAfterLast).isTrue()
}
+ private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
+ ranks.forEach { rank ->
+ execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
+ }
+ }
+
+ private fun SupportSQLiteDatabase.verifyRanksInOrder(ranks: List<FakeCommunalItemRank>) {
+ val cursor = query("SELECT * FROM communal_item_rank_table ORDER BY uid")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ ranks.forEach { rank ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("rank"))).isEqualTo(rank.rank)
+ cursor.moveToNext()
+ }
+
+ // Verify there is no more columns
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
/**
* Returns the expected data after migration from V1 to V2, which is simply that the new user
* serial number field is now set to [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED].
@@ -143,6 +238,10 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
val userSerialNumber: Int,
)
+ private data class FakeCommunalItemRank(
+ val rank: Int,
+ )
+
companion object {
private const val DATABASE_NAME = "communal_db"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 0b7a3ed3a7c2..6aecc0e2b8fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -22,6 +22,7 @@ import android.hardware.biometrics.BiometricSourceType
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.keyguard.keyguardUpdateMonitor
import com.android.keyguard.trustManager
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig
import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -52,12 +54,15 @@ import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -113,6 +118,7 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
powerInteractor,
fakeBiometricSettingsRepository,
trustManager,
+ { kosmos.sceneInteractor },
deviceEntryFaceAuthStatusInteractor,
)
}
@@ -279,6 +285,22 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainerEnabled_faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+ testScope.runTest {
+ underTest.start()
+
+ kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, false))
+ }
+
+ @Test
fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
testScope.runTest {
underTest.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index a0fe538bdd2b..3cbbb648af94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -62,26 +63,19 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val testScope = kosmos.testScope
- private lateinit var dismissInteractorWithDependencies:
- KeyguardDismissInteractorFactory.WithDependencies
+ private lateinit var dismissInteractor: KeyguardDismissInteractor
private lateinit var underTest: KeyguardDismissActionInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- dismissInteractorWithDependencies =
- KeyguardDismissInteractorFactory.create(
- context = context,
- testScope = testScope,
- keyguardRepository = keyguardRepository,
- )
-
+ dismissInteractor = kosmos.keyguardDismissInteractor
underTest =
KeyguardDismissActionInteractor(
repository = keyguardRepository,
transitionInteractor = kosmos.keyguardTransitionInteractor,
- dismissInteractor = dismissInteractorWithDependencies.interactor,
+ dismissInteractor = dismissInteractor,
applicationScope = testScope.backgroundScope,
sceneInteractor = kosmos.sceneInteractor,
deviceEntryInteractor = kosmos.deviceEntryInteractor,
@@ -166,9 +160,7 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
willAnimateOnLockscreen = true,
)
)
- dismissInteractorWithDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(
- true
- )
+ kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true)
assertThat(executeDismissAction).isEqualTo(onDismissAction)
}
@@ -307,8 +299,7 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
@Test
fun setKeyguardDone() =
testScope.runTest {
- val keyguardDoneTiming by
- collectLastValue(dismissInteractorWithDependencies.interactor.keyguardDone)
+ val keyguardDoneTiming by collectLastValue(dismissInteractor.keyguardDone)
runCurrent()
underTest.setKeyguardDone(KeyguardDone.LATER)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
index ecb46bdd06c4..fabed03bc18c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
@@ -23,11 +23,18 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.TrustGrantFlags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -38,14 +45,16 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardDismissInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
private lateinit var dispatcher: TestDispatcher
private lateinit var testScope: TestScope
- private lateinit var underTestDependencies: KeyguardDismissInteractorFactory.WithDependencies
- private lateinit var underTest: KeyguardDismissInteractor
+ private val underTest = kosmos.keyguardDismissInteractor
private val userInfo = UserInfo(0, "", 0)
@Before
@@ -54,13 +63,7 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
dispatcher = StandardTestDispatcher()
testScope = TestScope(dispatcher)
- underTestDependencies =
- KeyguardDismissInteractorFactory.create(
- context = context,
- testScope = testScope,
- )
- underTest = underTestDependencies.interactor
- underTestDependencies.userRepository.setUserInfos(listOf(userInfo))
+ kosmos.fakeUserRepository.setUserInfos(listOf(userInfo))
}
@Test
@@ -69,10 +72,10 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
val dismissKeyguardRequestWithoutImmediateDismissAction by
collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
- underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(null)
+ kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null)
assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
- underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(true)
+ kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true)
assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
}
@@ -81,7 +84,7 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
testScope.runTest {
val dismissKeyguardRequestWithoutImmediateDismissAction by
collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
- underTestDependencies.trustRepository.setRequestDismissKeyguard(
+ kosmos.fakeTrustRepository.setRequestDismissKeyguard(
TrustModel(
true,
0,
@@ -90,8 +93,8 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
)
assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
- underTestDependencies.powerRepository.setInteractive(true)
- underTestDependencies.trustRepository.setRequestDismissKeyguard(
+ kosmos.fakePowerRepository.setInteractive(true)
+ kosmos.fakeTrustRepository.setRequestDismissKeyguard(
TrustModel(
true,
0,
@@ -106,15 +109,15 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
testScope.runTest {
val dismissKeyguardRequestWithoutImmediateDismissAction by
collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
- underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+ kosmos.fakeUserRepository.setSelectedUserInfo(userInfo)
runCurrent()
// authenticated different user
- underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(22)
+ kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedPrimaryAuth(22)
assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
// authenticated correct user
- underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(userInfo.id)
+ kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedPrimaryAuth(userInfo.id)
assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
}
@@ -123,17 +126,15 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
testScope.runTest {
val dismissKeyguardRequestWithoutImmediateDismissAction by
collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
- underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+ kosmos.fakeUserRepository.setSelectedUserInfo(userInfo)
runCurrent()
// requested from different user
- underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
- 22
- )
+ kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(22)
assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
// requested from correct user
- underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+ kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
userInfo.id
)
assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
@@ -159,10 +160,10 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
val dismissKeyguardRequestWithImmediateDismissAction by
collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction)
- underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+ kosmos.fakeUserRepository.setSelectedUserInfo(userInfo)
runCurrent()
- underTestDependencies.keyguardRepository.setDismissAction(
+ kosmos.fakeKeyguardRepository.setDismissAction(
DismissAction.RunImmediately(
onDismissAction = { KeyguardDone.IMMEDIATE },
onCancelAction = {},
@@ -170,7 +171,7 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
willAnimateOnLockscreen = true,
)
)
- underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+ kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
userInfo.id
)
assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
@@ -184,10 +185,10 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
val dismissKeyguardRequestWithImmediateDismissAction by
collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction)
- underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+ kosmos.fakeUserRepository.setSelectedUserInfo(userInfo)
runCurrent()
- underTestDependencies.keyguardRepository.setDismissAction(
+ kosmos.fakeKeyguardRepository.setDismissAction(
DismissAction.RunAfterKeyguardGone(
dismissAction = {},
onCancelAction = {},
@@ -195,7 +196,7 @@ class KeyguardDismissInteractorTest : SysuiTestCase() {
willAnimateOnLockscreen = true,
)
)
- underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+ kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
userInfo.id
)
assertThat(dismissKeyguardRequestWithImmediateDismissAction).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index 2021400b326a..664a0bdedec4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.policy.IKeyguardDismissCallback
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -31,7 +30,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -64,31 +62,13 @@ class AlternateBouncerViewModelTest : SysuiTestCase() {
@Test
fun onRemovedFromWindow() =
testScope.runTest {
- kosmos.primaryBouncerInteractor.setDismissAction(
- mock(ActivityStarter.OnDismissAction::class.java),
- {},
- )
- assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNotNull()
-
- val dismissCallback = mock(IKeyguardDismissCallback::class.java)
- kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
underTest.onRemovedFromWindow()
-
- kosmos.fakeExecutor.runAllReady()
verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
- verify(dismissCallback).onDismissCancelled()
- assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNull()
}
@Test
fun onBackRequested() =
testScope.runTest {
- kosmos.primaryBouncerInteractor.setDismissAction(
- mock(ActivityStarter.OnDismissAction::class.java),
- {},
- )
- assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNotNull()
-
val dismissCallback = mock(IKeyguardDismissCallback::class.java)
kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
@@ -96,7 +76,6 @@ class AlternateBouncerViewModelTest : SysuiTestCase() {
kosmos.fakeExecutor.runAllReady()
verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
verify(dismissCallback).onDismissCancelled()
- assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNull()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index fc7f69319261..d13419eed281 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.dock.DockManagerFake
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
@@ -152,9 +153,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC
dockManager = DockManagerFake()
biometricSettingsRepository = FakeBiometricSettingsRepository()
val featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
- }
+ FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) }
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
val keyguardInteractor = withDeps.keyguardInteractor
@@ -223,6 +222,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC
broadcastDispatcher = broadcastDispatcher,
accessibilityManager = accessibilityManager,
pulsingGestureListener = kosmos.pulsingGestureListener,
+ faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
)
underTest =
KeyguardBottomAreaViewModel(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
index 67517a25ec87..67517a25ec87 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index 7d5722035a14..c7acd78c5623 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
@@ -17,14 +17,8 @@
package com.android.systemui.lifecycle
import android.view.View
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -32,8 +26,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.util.Assert
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -157,51 +149,9 @@ class SysUiViewModelTest : SysuiTestCase() {
assertThat(viewModel.isActivated).isTrue()
}
-
- @Test
- fun hydratedStateOf() {
- val keepAliveMutable = mutableStateOf(true)
- val upstreamStateFlow = MutableStateFlow(true)
- val upstreamFlow = upstreamStateFlow.map { !it }
- composeRule.setContent {
- val keepAlive by keepAliveMutable
- if (keepAlive) {
- val viewModel = rememberViewModel {
- FakeSysUiViewModel(
- upstreamFlow = upstreamFlow,
- upstreamStateFlow = upstreamStateFlow,
- )
- }
-
- Column {
- Text(
- "upstreamStateFlow=${viewModel.stateBackedByStateFlow}",
- Modifier.testTag("upstreamStateFlow")
- )
- Text(
- "upstreamFlow=${viewModel.stateBackedByFlow}",
- Modifier.testTag("upstreamFlow")
- )
- }
- }
- }
-
- composeRule.waitForIdle()
- composeRule
- .onNode(hasTestTag("upstreamStateFlow"))
- .assertTextEquals("upstreamStateFlow=true")
- composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=false")
-
- composeRule.runOnUiThread { upstreamStateFlow.value = false }
- composeRule.waitForIdle()
- composeRule
- .onNode(hasTestTag("upstreamStateFlow"))
- .assertTextEquals("upstreamStateFlow=false")
- composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=true")
- }
}
-private class FakeViewModel : SysUiViewModel() {
+private class FakeViewModel : SysUiViewModel, ExclusiveActivatable() {
var isActivated = false
override suspend fun onActivated(): Nothing {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index bdee9365d494..fd53b5baece5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -38,19 +38,28 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Bundle
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.Flags.MEDIA_REMOTE_RESUME
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_RECOMMENDATIONS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_SESSIONS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
@@ -58,16 +67,21 @@ import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERI
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.MediaControllerFactory
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -91,6 +105,8 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
@@ -111,13 +127,13 @@ private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
}
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class LegacyMediaDataManagerImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock lateinit var mediaControllerFactory: MediaControllerFactory
@Mock lateinit var controller: MediaController
@Mock lateinit var transportControls: MediaController.TransportControls
@Mock lateinit var playbackInfo: MediaController.PlaybackInfo
@@ -136,7 +152,6 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Mock lateinit var mediaDataFilter: LegacyMediaDataFilterImpl
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
- @Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var smartspaceManager: SmartspaceManager
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@@ -144,7 +159,6 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
lateinit var validRecommendationList: List<SmartspaceAction>
@Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
- @Mock private lateinit var mediaFlags: MediaFlags
@Mock private lateinit var logger: MediaUiEventLogger
lateinit var mediaDataManager: LegacyMediaDataManagerImpl
lateinit var mediaNotification: StatusBarNotification
@@ -159,6 +173,26 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Mock private lateinit var ugm: IUriGrantsManager
@Mock private lateinit var imageSource: ImageDecoder.Source
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.progressionOf(
+ Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER
+ )
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val kosmos = testKosmos()
+ private val testDispatcher = kosmos.testDispatcher
+ private val testScope = kosmos.testScope
+ private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+ private val activityStarter = kosmos.activityStarter
+ private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
private val originalSmartspaceSetting =
@@ -188,12 +222,16 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
1
)
+
mediaDataManager =
LegacyMediaDataManagerImpl(
context = context,
backgroundExecutor = backgroundExecutor,
+ backgroundDispatcher = testDispatcher,
uiExecutor = uiExecutor,
foregroundExecutor = foregroundExecutor,
+ mainDispatcher = testDispatcher,
+ applicationScope = testScope,
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
@@ -209,10 +247,11 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
useQsMediaPlayer = true,
systemClock = clock,
tunerService = tunerService,
- mediaFlags = mediaFlags,
+ mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
keyguardUpdateMonitor = keyguardUpdateMonitor,
+ mediaDataLoader = { kosmos.mediaDataLoader },
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -248,7 +287,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
- whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+ mediaControllerFactory.setControllerForToken(session.sessionToken, controller)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -278,10 +317,11 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, false)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, false)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, false)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, false)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, false)
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
}
@@ -310,49 +350,51 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
}
@Test
- fun testsetInactive_resume_dismissesMedia() {
- // WHEN resume controls are present, and time out
- val desc =
- MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
-
- backgroundExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
+ fun testsetInactive_resume_dismissesMedia() =
+ testScope.runTest {
+ // WHEN resume controls are present, and time out
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
)
- mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
- verify(logger)
- .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+ runCurrent()
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
- // THEN it is removed and listeners are informed
- foregroundExecutor.advanceClockToLast()
- foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
- }
+ mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
+ verify(logger)
+ .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+
+ // THEN it is removed and listeners are informed
+ foregroundExecutor.advanceClockToLast()
+ foregroundExecutor.runAllReady()
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
+ }
@Test
fun testLoadsMetadataOnBackground() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.numPending()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
}
@Test
@@ -370,8 +412,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -389,8 +430,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -417,11 +457,9 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testOnMetaDataLoaded_conservesActiveFlag() {
- whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -465,8 +503,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
}
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -552,8 +589,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then a media control is created with a placeholder title string
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -583,8 +619,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then a media control is created with a placeholder title string
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -625,8 +660,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then the media control is added using the notification's title
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -734,8 +768,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
// GIVEN that the manager has two notifications with resume actions
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+ testScope.assertRunAllReady(foreground = 2, background = 2)
verify(listener)
.onMediaDataLoaded(
@@ -822,7 +855,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() {
// With the flag enabled to allow remote media to resume
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
// GIVEN that the manager has a notification with a resume action, but is not local
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -853,7 +886,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() {
// With the flag enabled to allow remote media to resume
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
// GIVEN that the manager has a remote cast notification
addNotificationAndLoad(remoteCastNotification)
@@ -972,7 +1005,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testAddResumptionControls_hasPartialProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added with partial progress
val progress = 0.5
@@ -999,7 +1032,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testAddResumptionControls_hasNotPlayedProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have not been played
val extras =
@@ -1024,7 +1057,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testAddResumptionControls_hasFullProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added with progress info
val extras =
@@ -1050,7 +1083,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testAddResumptionControls_hasNoExtras() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that do not have any extras
val desc =
@@ -1068,7 +1101,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testAddResumptionControls_hasEmptyTitle() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have empty title
val desc =
@@ -1087,8 +1120,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
)
// Resumption controls are not added.
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
verify(listener, never())
.onMediaDataLoaded(
eq(PACKAGE_NAME),
@@ -1102,7 +1134,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testAddResumptionControls_hasBlankTitle() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have a blank title
val desc =
@@ -1121,8 +1153,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
)
// Resumption controls are not added.
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
verify(listener, never())
.onMediaDataLoaded(
eq(PACKAGE_NAME),
@@ -1189,8 +1220,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, notif)
// THEN it still loads
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1307,7 +1337,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1333,7 +1363,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
val extras =
Bundle().apply {
putString("package_name", PACKAGE_NAME)
@@ -1367,7 +1397,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1399,7 +1429,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testSetRecommendationInactive_notifiesListeners() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1479,8 +1509,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
fun testOnMediaDataTimedOut_updatesLastActiveTime() {
// GIVEN that the manager has a notification
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// WHEN the notification times out
clock.advanceTime(100)
@@ -1588,8 +1617,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
// WHEN the notification is loaded
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// THEN only the first MAX_COMPACT_ACTIONS are actually set
verify(listener)
@@ -1624,8 +1652,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
// WHEN the notification is loaded
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
verify(listener)
@@ -1644,7 +1671,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testPlaybackActions_noState_usesNotification() {
val desc = "Notification Action"
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
whenever(controller.playbackState).thenReturn(null)
val notifWithAction =
@@ -1659,8 +1686,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
}
mediaDataManager.onNotificationAdded(KEY, notifWithAction)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1679,7 +1705,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testPlaybackActions_hasPrevNext() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions =
PlaybackState.ACTION_PLAY or
PlaybackState.ACTION_SKIP_TO_PREVIOUS or
@@ -1723,7 +1749,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testPlaybackActions_noPrevNext_usesCustom() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
@@ -1755,7 +1781,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testPlaybackActions_connecting() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder =
PlaybackState.Builder()
@@ -1776,7 +1802,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
@@ -1814,7 +1840,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testPlaybackActions_playPause_hasButton() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY_PAUSE
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -1851,8 +1877,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
// update to remote cast
mediaDataManager.onNotificationAdded(KEY, remoteCastNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(logger)
.logPlaybackLocationChange(
anyInt(),
@@ -1914,7 +1939,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
whenever(controller.playbackState).thenReturn(state)
@@ -1935,46 +1960,48 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
}
@Test
- fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
- val desc =
- MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- val state =
- PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
- .setActions(PlaybackState.ACTION_PLAY_PAUSE)
- .build()
-
- // Add resumption controls in order to have semantic actions.
- // To make sure that they are not null after changing state.
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- backgroundExecutor.runAllReady()
- foregroundExecutor.runAllReady()
-
- stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+ fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() =
+ testScope.runTest {
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ val state =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+ .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+ .build()
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(PACKAGE_NAME),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
+ // Add resumption controls in order to have semantic actions.
+ // To make sure that they are not null after changing state.
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
)
- assertThat(mediaDataCaptor.value.isPlaying).isFalse()
- assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
- }
+ runCurrent()
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+
+ stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(PACKAGE_NAME),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+ assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+ }
@Test
fun testPlaybackStateNull_Pause_keyExists_callsListener() {
@@ -2036,7 +2063,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testRetain_notifPlayer_notifRemoved_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added, times out, and then removed
addNotificationAndLoad()
@@ -2066,7 +2093,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added and times out
addNotificationAndLoad()
@@ -2084,7 +2111,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added and then removed, without timing out
addNotificationAndLoad()
@@ -2101,7 +2128,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testRetain_canResume_removeWhileActive_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control that supports resumption is added
addNotificationAndLoad()
@@ -2133,8 +2160,8 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2153,8 +2180,8 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2187,8 +2214,8 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions is added, and then the session is destroyed
@@ -2207,8 +2234,8 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions and that does allow resumption is added,
@@ -2241,7 +2268,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2268,7 +2295,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions is added, and then the session is destroyed
@@ -2287,7 +2314,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions and that does allow resumption is added,
@@ -2320,8 +2347,8 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
@Test
fun testSessionDestroyed_noNotificationKey_stillRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
// When a notiifcation is added and then removed before it is fully processed
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
@@ -2392,6 +2419,23 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
assertThat(mediaDataCaptor.value.artwork).isNull()
}
+ private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
+ runCurrent()
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ // It doesn't make much sense to count tasks when we use coroutines in loader
+ // so this check is skipped in that scenario.
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ } else {
+ if (background > 0) {
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(background)
+ }
+ if (foreground > 0) {
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(foreground)
+ }
+ }
+ }
+
/** Helper function to add a basic media notification and capture the resulting MediaData */
private fun addNotificationAndLoad() {
addNotificationAndLoad(mediaNotification)
@@ -2400,8 +2444,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
/** Helper function to add the given notification and capture the resulting MediaData */
private fun addNotificationAndLoad(sbn: StatusBarNotification) {
mediaDataManager.onNotificationAdded(KEY, sbn)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -2435,8 +2478,8 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() {
pendingIntent,
packageName
)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 3b541cd98f4e..99c5b7cdfdc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -38,6 +38,9 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Bundle
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper
@@ -48,10 +51,14 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.MediaDataRepository
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -69,6 +76,7 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.FakeSettings
@@ -79,6 +87,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -126,6 +135,7 @@ private fun <T> anyObject(): T {
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class MediaDataProcessorTest : SysuiTestCase() {
val kosmos = testKosmos()
@@ -146,7 +156,6 @@ class MediaDataProcessorTest : SysuiTestCase() {
@Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
@Mock lateinit var mediaDeviceManager: MediaDeviceManager
@Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
- @Mock lateinit var mediaDataFilter: MediaDataFilterImpl
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
@Mock lateinit var activityStarter: ActivityStarter
@@ -185,14 +194,14 @@ class MediaDataProcessorTest : SysuiTestCase() {
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
1
)
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
+ private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
private lateinit var staticMockSession: MockitoSession
@Before
fun setup() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
staticMockSession =
ExtendedMockito.mockitoSession()
.mockStatic<UriGrantsManager>(UriGrantsManager::class.java)
@@ -258,6 +267,7 @@ class MediaDataProcessorTest : SysuiTestCase() {
session = MediaSession(context, "MediaDataProcessorTestSession")
mediaNotification =
SbnBuilder().run {
+ setUser(UserHandle(USER_ID))
setPkg(PACKAGE_NAME)
modifyNotification(context).also {
it.setSmallIcon(android.R.drawable.ic_media_pause)
@@ -1798,6 +1808,85 @@ class MediaDataProcessorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun postWithPlaybackActions_drawablesReused() =
+ kosmos.testScope.runTest {
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val stateActions =
+ PlaybackState.ACTION_PAUSE or
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+ PlaybackState.ACTION_SKIP_TO_NEXT
+ val stateBuilder =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PLAYING, 0, 10f)
+ .setActions(stateActions)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+ val userEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+
+ mediaDataProcessor.addInternalListener(mediaDataFilter)
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ addNotificationAndLoad()
+
+ assertThat(userEntries).hasSize(1)
+ val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+ addNotificationAndLoad()
+
+ assertThat(userEntries).hasSize(1)
+ val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+ assertThat(secondSemanticActions.playOrPause?.icon)
+ .isEqualTo(firstSemanticActions.playOrPause?.icon)
+ assertThat(secondSemanticActions.playOrPause?.background)
+ .isEqualTo(firstSemanticActions.playOrPause?.background)
+ assertThat(secondSemanticActions.nextOrCustom?.icon)
+ .isEqualTo(firstSemanticActions.nextOrCustom?.icon)
+ assertThat(secondSemanticActions.prevOrCustom?.icon)
+ .isEqualTo(firstSemanticActions.prevOrCustom?.icon)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun postWithPlaybackActions_drawablesNotReused() =
+ kosmos.testScope.runTest {
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val stateActions =
+ PlaybackState.ACTION_PAUSE or
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+ PlaybackState.ACTION_SKIP_TO_NEXT
+ val stateBuilder =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PLAYING, 0, 10f)
+ .setActions(stateActions)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+ val userEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+
+ mediaDataProcessor.addInternalListener(mediaDataFilter)
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ addNotificationAndLoad()
+
+ assertThat(userEntries).hasSize(1)
+ val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+ addNotificationAndLoad()
+
+ assertThat(userEntries).hasSize(1)
+ val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+ assertThat(secondSemanticActions.playOrPause?.icon)
+ .isNotEqualTo(firstSemanticActions.playOrPause?.icon)
+ assertThat(secondSemanticActions.playOrPause?.background)
+ .isNotEqualTo(firstSemanticActions.playOrPause?.background)
+ assertThat(secondSemanticActions.nextOrCustom?.icon)
+ .isNotEqualTo(firstSemanticActions.nextOrCustom?.icon)
+ assertThat(secondSemanticActions.prevOrCustom?.icon)
+ .isNotEqualTo(firstSemanticActions.prevOrCustom?.icon)
+ }
+
+ @Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 514273042685..6a66c4087615 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -254,22 +254,17 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// AND that token results in a null route
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ val data = loadMediaAndCaptureDeviceData()
+
// THEN the device should be disabled
- val data = captureDeviceData(KEY)
assertThat(data.enabled).isFalse()
}
@Test
fun deviceEventOnAddNotification() {
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN the update is dispatched to the listener
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
assertThat(data.icon).isEqualTo(icon)
@@ -417,15 +412,40 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN it uses the route name (instead of device name)
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
}
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableReused() {
+ whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
+ whenever(routingSession.selectedRoutes).thenReturn(listOf("selectedRoute", "selectedRoute"))
+ whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+
+ val firstData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondData = loadMediaAndCaptureDeviceData()
+
+ assertThat(secondData.icon).isEqualTo(firstData.icon)
+ }
+
+ @Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableNotReused() {
+ whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
+ whenever(routingSession.selectedRoutes).thenReturn(listOf("selectedRoute", "selectedRoute"))
+ whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+
+ val firstData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondData = loadMediaAndCaptureDeviceData()
+
+ assertThat(secondData.icon).isNotEqualTo(firstData.icon)
+ }
+
@RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
@Test
fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
@@ -433,11 +453,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN the device is disabled and name is set to null
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.enabled).isFalse()
assertThat(data.name).isNull()
}
@@ -449,23 +466,48 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN the device is disabled and name and icon are set to "OTHER DEVICE".
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.enabled).isFalse()
assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device))
assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB)
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+ @EnableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableReused() {
+ whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+ context.orCreateTestableResources.removeOverride(R.drawable.ic_media_home_devices)
+
+ val firstData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondData = loadMediaAndCaptureDeviceData()
+
+ assertThat(secondData.icon).isEqualTo(firstData.icon)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+ @DisableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+ fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableNotReused() {
+ whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+ context.orCreateTestableResources.removeOverride(R.drawable.ic_media_home_devices)
+
+ val firstData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondData = loadMediaAndCaptureDeviceData()
+
+ assertThat(secondData.icon).isNotEqualTo(firstData.icon)
+ }
+
@RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
@Test
fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
// GIVEN a notif is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(listener)
// AND MR2Manager returns null for routing session
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -480,13 +522,12 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
assertThat(data.enabled).isFalse()
assertThat(data.name).isNull()
}
+
@RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
@Test
fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_returnOtherDevice() {
// GIVEN a notif is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(listener)
// AND MR2Manager returns null for routing session
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -507,9 +548,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Test
fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
// GIVEN a notif is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(listener)
// GIVEN that MR2Manager returns null for routing session
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -529,9 +568,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Test
fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevice() {
// GIVEN a notif is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(listener)
// GIVEN that MR2Manager returns null for routing session
whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -563,12 +600,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
// Then the device name is the PhoneMediaDevice string
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context))
}
@@ -582,12 +615,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
whenever(routingSession.isSystemSession).thenReturn(true)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
// Then the device name is the selected route name
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
}
@@ -597,11 +626,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(routingSession.name).thenReturn(null)
whenever(routingSession.isSystemSession).thenReturn(false)
// WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
// THEN the device is enabled and uses the current connected device name
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.name).isEqualTo(DEVICE_NAME)
assertThat(data.enabled).isTrue()
}
@@ -611,9 +637,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
// GIVEN a controller with local playback type
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(mr2)
// WHEN onAudioInfoChanged fires with remote playback type
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -630,9 +654,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(playbackInfo.getVolumeControlId()).thenReturn(null)
whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
// GIVEN a controller with local playback type
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(mr2)
// WHEN onAudioInfoChanged fires with a volume control id change
whenever(playbackInfo.getVolumeControlId()).thenReturn("placeholder id")
@@ -649,9 +671,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
// GIVEN a controller with remote playback type
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
reset(mr2)
// WHEN onAudioInfoChanged fires with remote playback type
val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
@@ -665,9 +685,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fun deviceIdChanged_informListener() {
// GIVEN a notification is added, with a particular device connected
whenever(device.id).thenReturn(DEVICE_ID)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
// and later the manager gets a new device ID
val deviceCallback = captureCallback()
@@ -694,9 +712,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// GIVEN a notification is added, with a particular device connected
whenever(device.id).thenReturn(DEVICE_ID)
whenever(device.name).thenReturn(DEVICE_NAME)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ loadMediaAndCaptureDeviceData()
// and later the manager gets a new device name
val deviceCallback = captureCallback()
@@ -725,12 +741,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(device.name).thenReturn(DEVICE_NAME)
val firstIcon = mock(Drawable::class.java)
whenever(device.icon).thenReturn(firstIcon)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
- val dataCaptor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
- verify(listener).onMediaDeviceChanged(eq(KEY), any(), dataCaptor.capture())
+ loadMediaAndCaptureDeviceData()
// and later the manager gets a callback with only the icon changed
val deviceCallback = captureCallback()
@@ -772,11 +784,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
setupBroadcastPackage(BROADCAST_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
@@ -791,11 +799,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
setupBroadcastPackage(BROADCAST_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isTrue()
assertThat(data.enabled).isTrue()
assertThat(data.name)
@@ -811,11 +815,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
setupBroadcastPackage(NORMAL_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isTrue()
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(BROADCAST_APP_NAME)
@@ -829,11 +829,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
setupLeAudioConfiguration(false)
broadcastCallback.onBroadcastStopped(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
}
@@ -846,11 +842,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
setupBroadcastPackage(BROADCAST_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
assertThat(data.enabled).isFalse()
assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
@@ -858,18 +850,90 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Test
@DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_LE_AUDIO_SHARING,
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+ )
+ fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesReused() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ val firstDeviceData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+ assertThat(firstDeviceData.icon).isEqualTo(secondDeviceData.icon)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_LEGACY_LE_AUDIO_SHARING,
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+ )
@EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
+ fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesNotReused() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ val firstDeviceData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+ assertThat(firstDeviceData.icon).isNotEqualTo(secondDeviceData.icon)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_LEGACY_LE_AUDIO_SHARING,
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableReused() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(true)
setupBroadcastPackage(NORMAL_APP_NAME)
broadcastCallback.onBroadcastStarted(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
+ val firstDeviceData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondDeviceData = loadMediaAndCaptureDeviceData()
- val data = captureDeviceData(KEY)
+ assertThat(firstDeviceData.icon).isEqualTo(secondDeviceData.icon)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_LE_AUDIO_SHARING,
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+ )
+ fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableNotReused() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(NORMAL_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ val firstDeviceData = loadMediaAndCaptureDeviceData()
+ reset(listener)
+ val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+ assertThat(firstDeviceData.icon).isNotEqualTo(secondDeviceData.icon)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(NORMAL_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
assertThat(data.enabled).isFalse()
assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
@@ -883,11 +947,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
setupLeAudioConfiguration(false)
broadcastCallback.onBroadcastStopped(1, 1)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
-
- val data = captureDeviceData(KEY)
+ val data = loadMediaAndCaptureDeviceData()
assertThat(data.showBroadcastButton).isFalse()
assertThat(data.name?.equals(context.getString(R.string.audio_sharing_description)))
.isFalse()
@@ -903,13 +963,21 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
val callback: BluetoothLeBroadcast.Callback =
object : BluetoothLeBroadcast.Callback {
override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastStartFailed(reason: Int) {}
+
override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastStopFailed(reason: Int) {}
+
override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
+
override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
+
override fun onBroadcastMetadataChanged(
broadcastId: Int,
metadata: BluetoothLeBroadcastMetadata
@@ -941,4 +1009,12 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())
return captor.getValue()
}
+
+ private fun loadMediaAndCaptureDeviceData(): MediaDeviceData {
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ return captureDeviceData(KEY)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index f8358c51ed5c..850916be35bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -186,7 +186,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
sceneInteractor = kosmos.sceneInteractor,
)
verify(configurationController).addCallback(capture(configListener))
- verify(mediaDataManager).addListener(capture(listener))
verify(visualStabilityProvider)
.addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCallback))
@@ -405,8 +404,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
}
+ @DisableSceneContainer
@Test
fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
+ verify(mediaDataManager).addListener(capture(listener))
+
testPlayerOrdering()
// If smartspace is prioritized
@@ -439,8 +441,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
}
+ @DisableSceneContainer
@Test
fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
+ verify(mediaDataManager).addListener(capture(listener))
+
testPlayerOrdering()
// playing paused player
listener.value.onMediaDataLoaded(
@@ -547,8 +552,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
+ @DisableSceneContainer
@Test
fun testMediaLoaded_ScrollToActivePlayer() {
+ verify(mediaDataManager).addListener(capture(listener))
+
listener.value.onMediaDataLoaded(
PLAYING_LOCAL,
null,
@@ -604,8 +612,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
)
}
+ @DisableSceneContainer
@Test
fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
+ verify(mediaDataManager).addListener(capture(listener))
+
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
@@ -647,8 +658,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
assertEquals(playerIndex, 0)
}
+ @DisableSceneContainer
@Test
fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
+ verify(mediaDataManager).addListener(capture(listener))
+
var result = false
mediaCarouselController.updateHostVisibility = { result = true }
@@ -658,8 +672,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
assertEquals(true, result)
}
+ @DisableSceneContainer
@Test
fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
+ verify(mediaDataManager).addListener(capture(listener))
+
var result = false
mediaCarouselController.updateHostVisibility = { result = true }
@@ -788,8 +805,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
verify(pageIndicator, times(4)).setNumPages(any())
}
+ @DisableSceneContainer
@Test
fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() {
+ verify(mediaDataManager).addListener(capture(listener))
+
testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded()
// When an update to existing smartspace data is loaded
@@ -804,8 +824,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
}
+ @DisableSceneContainer
@Test
fun testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() {
+ verify(mediaDataManager).addListener(capture(listener))
+
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
// When inactive smartspace data is loaded
@@ -1023,11 +1046,13 @@ class MediaCarouselControllerTest : SysuiTestCase() {
verify(panel).updateAnimatorDurationScale()
}
+ @DisableSceneContainer
@Test
fun swipeToDismiss_pausedAndResumeOff_userInitiated() {
+ verify(mediaDataManager).addListener(capture(listener))
+
// When resumption is disabled, paused media should be dismissed after being swiped away
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
-
val pausedMedia = DATA.copy(isPlaying = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
mediaCarouselController.onSwipeToDismiss()
@@ -1042,8 +1067,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
}
+ @DisableSceneContainer
@Test
fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() {
+ verify(mediaDataManager).addListener(capture(listener))
+
// When resumption is disabled, paused media should be dismissed after being swiped away
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
mediaCarouselController.updateHostVisibility = {}
@@ -1068,6 +1096,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
* @param function called when a certain configuration change occurs.
*/
private fun testConfigurationChange(function: () -> Unit) {
+ verify(mediaDataManager).addListener(capture(listener))
mediaCarouselController.pageIndicator = pageIndicator
listener.value.onMediaDataLoaded(
PLAYING_LOCAL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 521aa5a7352b..1260a65b9c1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -70,6 +70,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -84,7 +85,6 @@ import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.monet.ColorScheme
@@ -141,6 +141,7 @@ private const val APP_NAME = "APP_NAME"
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@DisableSceneContainer
public class MediaControlPanelTest : SysuiTestCase() {
@get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@@ -233,9 +234,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var recProgressBar1: SeekBar
@Mock private lateinit var recProgressBar2: SeekBar
@Mock private lateinit var recProgressBar3: SeekBar
- private var shouldShowBroadcastButton: Boolean = false
@Mock private lateinit var globalSettings: GlobalSettings
- @Mock private lateinit var mediaFlags: MediaFlags
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -254,7 +253,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
.thenReturn(applicationInfo)
whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
context.setMockPackageManager(packageManager)
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
player =
object :
@@ -278,7 +276,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
lockscreenUserManager,
broadcastDialogController,
globalSettings,
- mediaFlags,
) {
override fun loadAnimator(
animId: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 6c350cb4a5b0..2370bca52951 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -40,7 +41,6 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -85,6 +85,7 @@ import org.mockito.kotlin.anyOrNull
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@DisableSceneContainer
class MediaHierarchyManagerTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -105,7 +106,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var logger: MediaViewLogger
- @Mock private lateinit var mediaFlags: MediaFlags
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -139,7 +139,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
shadeExpansion = MutableStateFlow(0f)
whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade)
whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion)
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
mediaHierarchyManager =
MediaHierarchyManager(
context,
@@ -160,7 +159,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
testScope.backgroundScope,
ResourcesSplitShadeStateController(),
logger,
- mediaFlags,
)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index 00b9a46f340a..e765b6f77155 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -38,12 +38,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.CachingIconView
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
import com.android.systemui.surfaceeffects.ripple.MultiRippleView
@@ -113,7 +113,6 @@ class MediaViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mediaTitleWidgetState: WidgetState
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
- @Mock private lateinit var mediaFlags: MediaFlags
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
@Mock private lateinit var globalSettings: GlobalSettings
@@ -140,7 +139,6 @@ class MediaViewControllerTest : SysuiTestCase() {
logger,
seekBarViewModel,
mainExecutor,
- mediaFlags,
globalSettings,
) {
override fun loadAnimator(
@@ -374,10 +372,9 @@ class MediaViewControllerTest : SysuiTestCase() {
verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
}
+ @EnableSceneContainer
@Test
fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
getEnabledChangeListener().onEnabledChanged(enabled = false)
@@ -386,10 +383,9 @@ class MediaViewControllerTest : SysuiTestCase() {
.isEqualTo(ConstraintSet.INVISIBLE)
}
+ @EnableSceneContainer
@Test
fun attachPlayer_seekBarEnabled_seekBarVisible() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -397,10 +393,9 @@ class MediaViewControllerTest : SysuiTestCase() {
.isEqualTo(ConstraintSet.VISIBLE)
}
+ @EnableSceneContainer
@Test
fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -413,10 +408,9 @@ class MediaViewControllerTest : SysuiTestCase() {
.isEqualTo(ConstraintSet.INVISIBLE)
}
+ @EnableSceneContainer
@Test
fun attachPlayer_notScrubbing_scrubbingViewsGone() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.canShowScrubbingTime = true
getScrubbingChangeListener().onScrubbingChanged(true)
@@ -433,10 +427,9 @@ class MediaViewControllerTest : SysuiTestCase() {
.isEqualTo(ConstraintSet.GONE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.canShowScrubbingTime = false
getScrubbingChangeListener().onScrubbingChanged(true)
@@ -452,10 +445,9 @@ class MediaViewControllerTest : SysuiTestCase() {
.isEqualTo(ConstraintSet.GONE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true)
mediaViewController.setUpPrevButtonInfo(false)
@@ -474,10 +466,9 @@ class MediaViewControllerTest : SysuiTestCase() {
.isEqualTo(ConstraintSet.GONE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(false)
mediaViewController.setUpPrevButtonInfo(true)
@@ -496,10 +487,9 @@ class MediaViewControllerTest : SysuiTestCase() {
.isEqualTo(ConstraintSet.GONE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true)
mediaViewController.setUpPrevButtonInfo(true)
@@ -522,10 +512,9 @@ class MediaViewControllerTest : SysuiTestCase() {
.isEqualTo(ConstraintSet.VISIBLE)
}
+ @EnableSceneContainer
@Test
fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() {
- whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE)
mediaViewController.setUpPrevButtonInfo(true, ConstraintSet.INVISIBLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index f531a3fdd8f0..3e3aa4f079f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -16,9 +16,9 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index 8fbd3c8b7ebf..69b7b2bfcf8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -29,8 +29,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.util.mockito.mock
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import java.util.Optional
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index bfbb7ceee6b5..a770ee199ba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -18,7 +18,9 @@ package com.android.systemui.navigationbar;
import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -37,6 +39,9 @@ import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.provider.Flags;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -47,6 +52,7 @@ import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutT
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dump.DumpManager;
@@ -94,6 +100,8 @@ public class NavBarHelperTest extends SysuiTestCase {
@Mock
AccessibilityButtonTargetsObserver mAccessibilityButtonTargetObserver;
@Mock
+ AccessibilityGestureTargetsObserver mAccessibilityGestureTargetObserver;
+ @Mock
SystemActions mSystemActions;
@Mock
OverviewProxyService mOverviewProxyService;
@@ -152,6 +160,7 @@ public class NavBarHelperTest extends SysuiTestCase {
mAccessibilityManager).addAccessibilityServicesStateChangeListener(any());
mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
+ mAccessibilityGestureTargetObserver,
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
() -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
@@ -171,6 +180,7 @@ public class NavBarHelperTest extends SysuiTestCase {
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper);
verify(mAccessibilityButtonTargetObserver, times(1)).addListener(mNavBarHelper);
+ verify(mAccessibilityGestureTargetObserver, times(1)).addListener(mNavBarHelper);
verify(mAccessibilityManager, times(1)).addAccessibilityServicesStateChangeListener(
mNavBarHelper);
verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt());
@@ -185,6 +195,7 @@ public class NavBarHelperTest extends SysuiTestCase {
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
verify(mAccessibilityButtonModeObserver, times(1)).removeListener(mNavBarHelper);
verify(mAccessibilityButtonTargetObserver, times(1)).removeListener(mNavBarHelper);
+ verify(mAccessibilityGestureTargetObserver, times(1)).removeListener(mNavBarHelper);
verify(mAccessibilityManager, times(1)).removeAccessibilityServicesStateChangeListener(
mNavBarHelper);
verify(mWm, times(1)).removeRotationWatcher(any());
@@ -353,6 +364,83 @@ public class NavBarHelperTest extends SysuiTestCase {
verify(mEdgeBackGestureHandler, times(1)).onConfigurationChanged(any());
}
+ @Test
+ public void updateA11yState_navBarMode_softwareTargets_isClickable() {
+ when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+ when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE))
+ .thenReturn(createFakeShortcutTargets());
+
+ mNavBarHelper.updateA11yState();
+ long state = mNavBarHelper.getA11yButtonState();
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(
+ SYSUI_STATE_A11Y_BUTTON_CLICKABLE);
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(
+ SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+ public void updateA11yState_gestureMode_softwareTargets_isClickable() {
+ when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+ ACCESSIBILITY_BUTTON_MODE_GESTURE);
+ when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE))
+ .thenReturn(createFakeShortcutTargets());
+
+ mNavBarHelper.updateA11yState();
+ long state = mNavBarHelper.getA11yButtonState();
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(
+ SYSUI_STATE_A11Y_BUTTON_CLICKABLE);
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(
+ SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+ public void updateA11yState_gestureNavMode_floatingButtonMode_gestureTargets_isClickable() {
+ mNavBarHelper.onNavigationModeChanged(NAV_BAR_MODE_GESTURAL);
+ when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+ ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+ when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.GESTURE))
+ .thenReturn(createFakeShortcutTargets());
+
+ mNavBarHelper.updateA11yState();
+ long state = mNavBarHelper.getA11yButtonState();
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(
+ SYSUI_STATE_A11Y_BUTTON_CLICKABLE);
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(
+ SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+ public void updateA11yState_navBarMode_gestureTargets_isNotClickable() {
+ when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+ when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.GESTURE))
+ .thenReturn(createFakeShortcutTargets());
+
+ mNavBarHelper.updateA11yState();
+ long state = mNavBarHelper.getA11yButtonState();
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(0);
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+ public void updateA11yState_singleTarget_clickableButNotLongClickable() {
+ when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+ when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE))
+ .thenReturn(new ArrayList<>(List.of("a")));
+
+ mNavBarHelper.updateA11yState();
+ long state = mNavBarHelper.getA11yButtonState();
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(
+ SYSUI_STATE_A11Y_BUTTON_CLICKABLE);
+ assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(0);
+ }
+
private List<String> createFakeShortcutTargets() {
return new ArrayList<>(List.of("a", "b", "c", "d"));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 04d140c458e8..2905a7329d21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -85,6 +85,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dump.DumpManager;
@@ -287,6 +288,7 @@ public class NavigationBarTest extends SysuiTestCase {
mNavBarHelper = spy(new NavBarHelper(mContext, mock(AccessibilityManager.class),
mock(AccessibilityButtonModeObserver.class),
mock(AccessibilityButtonTargetsObserver.class),
+ mock(AccessibilityGestureTargetsObserver.class),
mSystemActions, mOverviewProxyService,
() -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
mKeyguardStateController, mock(NavigationModeController.class),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index d9faa30cb072..d9faa30cb072 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index ebab04989590..748c7d9d939b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -497,6 +497,17 @@ public class QSTileImplTest extends SysuiTestCase {
assertThat(mTile.mRefreshes).isEqualTo(1);
}
+ @Test
+ public void testStaleTriggeredOnUserSwitch() {
+ mTile.clearRefreshes();
+
+ mTile.userSwitch(10);
+ mTestableLooper.processAllMessages();
+
+ assertFalse(mTile.isListening());
+ assertThat(mTile.mRefreshes).isEqualTo(1);
+ }
+
private void assertEvent(UiEventLogger.UiEventEnum eventType,
UiEventLoggerFake.FakeUiEvent fakeEvent) {
assertEquals(eventType.getId(), fakeEvent.eventId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index b67e111af13d..5a5cdcd99054 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,7 +33,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BACK_GESTURE
import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
@@ -58,6 +57,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.controller.keyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
@@ -140,7 +140,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
}
testableLooper = TestableLooper.get(this)
@@ -186,7 +187,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
// First call succeeds.
@@ -214,7 +216,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
@@ -237,7 +240,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
kosmos.sceneDataSourceDelegator,
kosmos.notificationStackScrollLayoutController,
kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController
+ kosmos.lockscreenSmartspaceController,
+ logcatLogBuffer("GlanceableHubContainerControllerTest")
)
// Only initView without attaching a view as we don't want the flows to start collecting
@@ -437,7 +441,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
+ @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit() =
with(kosmos) {
testScope.runTest {
@@ -463,7 +467,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
@EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit_fullSwipe() =
with(kosmos) {
@@ -484,7 +487,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
+ @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit_rtl() =
with(kosmos) {
testScope.runTest {
@@ -509,7 +512,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
- @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
@EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun gestureExclusionZone_setAfterInit_rtl_fullSwipe() =
with(kosmos) {
@@ -530,102 +532,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
- @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun gestureExclusionZone_setAfterInit_backGestureEnabled() =
- with(kosmos) {
- testScope.runTest {
- whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- /* left= */ FAKE_INSETS.left,
- /* top= */ TOP_SWIPE_REGION_WIDTH,
- /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
- /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
- ),
- Rect(
- /* left= */ 0,
- /* top= */ 0,
- /* right= */ FAKE_INSETS.right,
- /* bottom= */ CONTAINER_HEIGHT
- )
- )
- }
- }
-
- @Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun gestureExclusionZone_setAfterInit_backGestureEnabled_fullSwipe() =
- with(kosmos) {
- testScope.runTest {
- whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- /* left= */ 0,
- /* top= */ 0,
- /* right= */ FAKE_INSETS.right,
- /* bottom= */ CONTAINER_HEIGHT
- )
- )
- }
- }
-
- @Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
- @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl() =
- with(kosmos) {
- testScope.runTest {
- whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- /* left= */ FAKE_INSETS.left,
- /* top= */ TOP_SWIPE_REGION_WIDTH,
- /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
- /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
- ),
- Rect(
- /* left= */ FAKE_INSETS.left,
- /* top= */ 0,
- /* right= */ CONTAINER_WIDTH,
- /* bottom= */ CONTAINER_HEIGHT
- )
- )
- }
- }
-
- @Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl_fullSwipe() =
- with(kosmos) {
- testScope.runTest {
- whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
- goToScene(CommunalScenes.Communal)
-
- assertThat(containerView.systemGestureExclusionRects)
- .containsExactly(
- Rect(
- Rect(
- /* left= */ FAKE_INSETS.left,
- /* top= */ 0,
- /* right= */ CONTAINER_WIDTH,
- /* bottom= */ CONTAINER_HEIGHT
- )
- )
- )
- }
- }
-
- @Test
fun gestureExclusionZone_unsetWhenShadeOpen() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 505f7997ef1c..3f6617b32131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -197,6 +197,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
() -> sceneInteractor,
() -> mKosmos.getFromGoneTransitionInteractor(),
() -> mKosmos.getFromLockscreenTransitionInteractor(),
+ () -> mKosmos.getFromOccludedTransitionInteractor(),
() -> mKosmos.getSharedNotificationContainerInteractor(),
mTestScope);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index fadb1d7c91a1..fadb1d7c91a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 50131cb06631..a0d231b8bb6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.app.Flags;
import android.app.Notification;
import android.content.Context;
import android.content.ContextWrapper;
@@ -41,9 +42,13 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Icon;
+import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.notification.StatusBarNotification;
import android.view.ViewGroup;
@@ -191,6 +196,34 @@ public class StatusBarIconViewTest extends SysuiTestCase {
}
@Test
+ @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+ public void setIcon_withPreloaded_usesPreloaded() {
+ Icon mockIcon = mock(Icon.class);
+ when(mockIcon.loadDrawableAsUser(any(), anyInt())).thenReturn(new ColorDrawable(1));
+ mStatusBarIcon.icon = mockIcon;
+ mStatusBarIcon.preloadedIcon = new ShapeDrawable();
+
+ mIconView.set(mStatusBarIcon);
+
+ assertThat(mIconView.getDrawable()).isNotNull();
+ assertThat(mIconView.getDrawable()).isInstanceOf(ShapeDrawable.class);
+ }
+
+ @Test
+ @DisableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+ public void setIcon_withPreloadedButFlagDisabled_ignoresPreloaded() {
+ Icon mockIcon = mock(Icon.class);
+ when(mockIcon.loadDrawableAsUser(any(), anyInt())).thenReturn(new ColorDrawable(1));
+ mStatusBarIcon.icon = mockIcon;
+ mStatusBarIcon.preloadedIcon = new ShapeDrawable();
+
+ mIconView.set(mStatusBarIcon);
+
+ assertThat(mIconView.getDrawable()).isNotNull();
+ assertThat(mIconView.getDrawable()).isInstanceOf(ColorDrawable.class);
+ }
+
+ @Test
public void testUpdateIconScale_constrainedDrawableSizeLessThanDpIconSize() {
int dpIconSize = 60;
int dpDrawingSize = 30;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index ad6aca1dcd4f..3c583f26b0df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
@@ -45,8 +46,8 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -66,6 +67,7 @@ class StackCoordinatorTest : SysuiTestCase() {
SensitiveNotificationProtectionController
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
+ @Mock private lateinit var row: ExpandableNotificationRow
@Before
fun setUp() {
@@ -74,6 +76,8 @@ class StackCoordinatorTest : SysuiTestCase() {
whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
entry = NotificationEntryBuilder().setSection(section).build()
+ entry.row = row
+ entry.setSensitive(false, false)
coordinator =
StackCoordinator(
groupExpansionManagerImpl,
@@ -189,4 +193,17 @@ class StackCoordinatorTest : SysuiTestCase() {
.setNotifStats(NotifStats(1, false, false, true, false))
verifyZeroInteractions(stackController)
}
+
+ @Test
+ @EnableFlags(
+ FooterViewRefactor.FLAG_NAME
+ )
+ fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() {
+ entry.setSensitive(true, true)
+ whenever(section.bucket).thenReturn(BUCKET_ALERTING)
+ afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ verify(activeNotificationsInteractor)
+ .setNotifStats(NotifStats(1, hasNonClearableAlertingNotifs = true, false, false, false))
+ verifyZeroInteractions(stackController)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index d1b1f466ef7a..ed99705b194e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -98,16 +98,20 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
// instead of VisualInterruptionDecisionProviderTestBase
// because avalanche code is based on the suppression refactor.
+ private fun getAvalancheSuppressor() : AvalancheSuppressor {
+ return AvalancheSuppressor(
+ avalancheProvider, systemClock, settingsInteractor, packageManager,
+ uiEventLogger, context, notificationManager, logger
+ )
+ }
+
@Test
fun testAvalancheFilter_suppress_hasNotSeenEdu_showEduHun() {
setAllowedEmergencyPkg(false)
whenever(avalancheProvider.timeoutMs).thenReturn(20)
whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
- val avalancheSuppressor = AvalancheSuppressor(
- avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager
- )
+ val avalancheSuppressor = getAvalancheSuppressor()
avalancheSuppressor.hasSeenEdu = false
withFilter(avalancheSuppressor) {
@@ -128,10 +132,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
whenever(avalancheProvider.timeoutMs).thenReturn(20)
whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
- val avalancheSuppressor = AvalancheSuppressor(
- avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager
- )
+ val avalancheSuppressor = getAvalancheSuppressor()
avalancheSuppressor.hasSeenEdu = true
withFilter(avalancheSuppressor) {
@@ -151,8 +152,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -171,8 +171,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldNotHeadsUp(
@@ -191,8 +190,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -209,8 +207,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -227,8 +224,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -245,8 +241,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -263,8 +258,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -281,8 +275,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -300,8 +293,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -318,8 +310,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
assertFsiNotSuppressed()
}
@@ -330,8 +321,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -359,8 +349,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
setAllowedEmergencyPkg(true)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
- uiEventLogger, context, notificationManager)
+ getAvalancheSuppressor()
) {
ensurePeekState()
assertShouldHeadsUp(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 9d3d9c156238..284efc71a96d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -139,6 +139,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
protected val settingsInteractor: NotificationSettingsInteractor = mock()
protected val packageManager: PackageManager = mock()
protected val notificationManager: NotificationManager = mock()
+ protected val logger: VisualInterruptionDecisionLogger = mock()
protected abstract val provider: VisualInterruptionDecisionProvider
private val neverSuppresses = object : NotificationInterruptSuppressor {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 491919a16a4e..30a1214d69d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -35,6 +35,9 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -74,6 +77,7 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -125,8 +129,10 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
): RichOngoingContentModel? = fakeRonContentModel
}
- private var fakeRonViewHolder: InflatedContentViewHolder? = null
- private val fakeRonViewInflater =
+ private var fakeContractedRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeExpandedRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeHeadsUpRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeRonViewInflater =
spy(
object : RichOngoingNotificationViewInflater {
override fun inflateView(
@@ -134,8 +140,20 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
existingView: View?,
entry: NotificationEntry,
systemUiContext: Context,
- parentView: ViewGroup
- ): InflatedContentViewHolder? = fakeRonViewHolder
+ parentView: ViewGroup,
+ viewType: RichOngoingNotificationViewType
+ ): ContentViewInflationResult =
+ when (viewType) {
+ RichOngoingNotificationViewType.Contracted -> fakeContractedRonViewHolder
+ RichOngoingNotificationViewType.Expanded -> fakeExpandedRonViewHolder
+ RichOngoingNotificationViewType.HeadsUp -> fakeHeadsUpRonViewHolder
+ }
+
+ override fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean = false
}
)
@@ -149,6 +167,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
.setContentText("Text")
.setStyle(Notification.BigTextStyle().bigText("big text"))
testHelper = NotificationTestHelper(mContext, mDependency)
+ testHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL)
row = spy(testHelper.createRow(builder.build()))
notificationInflater =
NotificationRowContentBinderImpl(
@@ -388,15 +407,62 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@Test
fun testRonModelRequiredForRonView() {
fakeRonContentModel = null
+ val contractedRonView = View(context)
+ val expandedRonView = View(context)
+ val headsUpRonView = View(context)
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = contractedRonView, binder = mock())
+ fakeExpandedRonViewHolder =
+ InflatedContentViewHolder(view = expandedRonView, binder = mock())
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = headsUpRonView, binder = mock())
+
+ // WHEN inflater inflates
+ val contentToInflate =
+ FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
+ inflateAndWait(notificationInflater, contentToInflate, row)
+ verifyZeroInteractions(fakeRonViewInflater)
+ }
+
+ @Test
+ fun testRonModelCleansUpRemoteViews() {
val ronView = View(context)
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
+
+ val entry = row.entry
+
+ fakeRonContentModel = mock<TimerContentModel>()
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = ronView, binder = mock<DeferredContentViewBinder>())
+
// WHEN inflater inflates
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
- verify(fakeRonViewInflater, never()).inflateView(any(), any(), any(), any(), any())
+
+ // VERIFY
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_CONTRACTED))
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_EXPANDED))
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_HEADS_UP))
}
@Test
- fun testRonModelTriggersInflationOfRonView() {
+ fun testRonModelCleansUpSmartReplies() {
+ val ronView = View(context)
+
+ val privateLayout = spy(row.privateLayout)
+
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mock<TimerContentModel>()
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // VERIFY
+ verify(privateLayout).setExpandedInflatedSmartReplies(eq(null))
+ verify(privateLayout).setHeadsUpInflatedSmartReplies(eq(null))
+ }
+
+ @Test
+ fun testRonModelTriggersInflationOfContractedRonView() {
val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
val mockBinder = mock<DeferredContentViewBinder>()
@@ -405,18 +471,229 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
val privateLayout = row.privateLayout
fakeRonContentModel = mockRonModel
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
// WHEN inflater inflates
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
// VERIFY that the inflater is invoked
verify(fakeRonViewInflater)
- .inflateView(eq(mockRonModel), any(), eq(entry), any(), eq(privateLayout))
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.Contracted)
+ )
assertThat(row.privateLayout.contractedChild).isSameInstanceAs(ronView)
verify(mockBinder).setupContentViewBinder()
}
@Test
- fun ronViewAppliesElementsInOrder() {
+ fun testRonModelTriggersInflationOfExpandedRonView() {
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ val entry = row.entry
+ val privateLayout = row.privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // VERIFY that the inflater is invoked
+ verify(fakeRonViewInflater)
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.Expanded)
+ )
+ assertThat(row.privateLayout.expandedChild).isSameInstanceAs(ronView)
+ verify(mockBinder).setupContentViewBinder()
+ }
+
+ @Test
+ fun testRonModelTriggersInflationOfHeadsUpRonView() {
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ val entry = row.entry
+ val privateLayout = row.privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // VERIFY that the inflater is invoked
+ verify(fakeRonViewInflater)
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.HeadsUp)
+ )
+ assertThat(row.privateLayout.headsUpChild).isSameInstanceAs(ronView)
+ verify(mockBinder).setupContentViewBinder()
+ }
+
+ @Test
+ fun keepExistingViewForContractedRonNotChangingContractedChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mContractedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeContractedRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // THEN do not dispose old contracted binder handle and change contracted child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setContractedChild(any())
+ }
+
+ @Test
+ fun keepExistingViewForExpandedRonNotChangingExpandedChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // THEN do not dispose old expanded binder handle and change expanded child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setExpandedChild(any())
+ }
+
+ @Test
+ fun keepExistingViewForHeadsUpRonNotChangingHeadsUpChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // THEN - do not dispose old heads up binder handle and change heads up child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setHeadsUpChild(any())
+ }
+
+ @Test
+ fun nullContentViewForContractedRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mContractedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeContractedRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setContractedChild(eq(null))
+ }
+ }
+
+ @Test
+ fun nullContentViewForExpandedRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setExpandedChild(eq(null))
+ }
+ }
+
+ @Test
+ fun nullContentViewForHeadsUpRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setHeadsUpChild(eq(null))
+ }
+ }
+
+ @Test
+ fun contractedRonViewAppliesElementsInOrder() {
val oldHandle = mock<DisposableHandle>()
val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
@@ -429,7 +706,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
row.privateLayout = privateLayout
fakeRonContentModel = mockRonModel
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
// WHEN inflater inflates
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
@@ -443,16 +721,89 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
}
@Test
- fun testRonNotReinflating() {
- val handle0 = mock<DisposableHandle>()
- val handle1 = mock<DisposableHandle>()
+ fun expandedRonViewAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, mockBinder) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setExpandedChild(eq(ronView))
+ verify(mockBinder).setupContentViewBinder()
+ }
+ }
+
+ @Test
+ fun headsUpRonViewAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, mockBinder) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setHeadsUpChild(eq(ronView))
+ verify(mockBinder).setupContentViewBinder()
+ }
+ }
+
+ @Test
+ fun testRonNotReinflating() {
+ val oldContractedBinderHandle = mock<DisposableHandle>()
+ val oldExpandedBinderHandle = mock<DisposableHandle>()
+ val oldHeadsUpBinderHandle = mock<DisposableHandle>()
+
+ val contractedBinderHandle = mock<DisposableHandle>()
+ val expandedBinderHandle = mock<DisposableHandle>()
+ val headsUpBinderHandle = mock<DisposableHandle>()
+
+ val contractedRonView = View(context)
+ val expandedRonView = View(context)
+ val headsUpRonView = View(context)
+
val mockRonModel1 = mock<TimerContentModel>()
val mockRonModel2 = mock<TimerContentModel>()
- val mockBinder1 = mock<DeferredContentViewBinder>()
- doReturn(handle1).whenever(mockBinder1).setupContentViewBinder()
- row.privateLayout.mContractedBinderHandle = handle0
+ val mockContractedViewBinder = mock<DeferredContentViewBinder>()
+ val mockExpandedViewBinder = mock<DeferredContentViewBinder>()
+ val mockHeadsUpViewBinder = mock<DeferredContentViewBinder>()
+
+ doReturn(contractedBinderHandle).whenever(mockContractedViewBinder).setupContentViewBinder()
+ doReturn(expandedBinderHandle).whenever(mockExpandedViewBinder).setupContentViewBinder()
+ doReturn(headsUpBinderHandle).whenever(mockHeadsUpViewBinder).setupContentViewBinder()
+
+ row.privateLayout.mContractedBinderHandle = oldContractedBinderHandle
+ row.privateLayout.mExpandedBinderHandle = oldExpandedBinderHandle
+ row.privateLayout.mHeadsUpBinderHandle = oldHeadsUpBinderHandle
val entry = spy(row.entry)
row.entry = entry
val privateLayout = spy(row.privateLayout)
@@ -460,31 +811,87 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
// WHEN inflater inflates both a model and a view
fakeRonContentModel = mockRonModel1
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder1)
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = contractedRonView, binder = mockContractedViewBinder)
+ fakeExpandedRonViewHolder =
+ InflatedContentViewHolder(view = expandedRonView, binder = mockExpandedViewBinder)
+ fakeHeadsUpRonViewHolder =
+ InflatedContentViewHolder(view = headsUpRonView, binder = mockHeadsUpViewBinder)
+
+ val contentToInflate =
+ FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
+ inflateAndWait(notificationInflater, contentToInflate, row)
// Validate that these 4 steps happen in this precise order
- inOrder(handle0, entry, privateLayout, mockBinder1, handle1) {
- verify(handle0).dispose()
+ inOrder(
+ oldContractedBinderHandle,
+ oldExpandedBinderHandle,
+ oldHeadsUpBinderHandle,
+ entry,
+ privateLayout,
+ mockContractedViewBinder,
+ mockExpandedViewBinder,
+ mockHeadsUpViewBinder,
+ contractedBinderHandle,
+ expandedBinderHandle,
+ headsUpBinderHandle
+ ) {
+ verify(oldContractedBinderHandle).dispose()
+ verify(oldExpandedBinderHandle).dispose()
+ verify(oldHeadsUpBinderHandle).dispose()
+
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel1 })
- verify(privateLayout).setContractedChild(eq(ronView))
- verify(mockBinder1).setupContentViewBinder()
- verify(handle1, never()).dispose()
+
+ verify(privateLayout).setContractedChild(eq(contractedRonView))
+ verify(mockContractedViewBinder).setupContentViewBinder()
+
+ verify(privateLayout).setExpandedChild(eq(expandedRonView))
+ verify(mockExpandedViewBinder).setupContentViewBinder()
+
+ verify(privateLayout).setHeadsUpChild(eq(headsUpRonView))
+ verify(mockHeadsUpViewBinder).setupContentViewBinder()
+
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
}
- clearInvocations(handle0, entry, privateLayout, mockBinder1, handle1)
+ clearInvocations(
+ oldContractedBinderHandle,
+ oldExpandedBinderHandle,
+ oldHeadsUpBinderHandle,
+ entry,
+ privateLayout,
+ mockContractedViewBinder,
+ mockExpandedViewBinder,
+ mockHeadsUpViewBinder,
+ contractedBinderHandle,
+ expandedBinderHandle,
+ headsUpBinderHandle
+ )
// THEN when the inflater inflates just a model
fakeRonContentModel = mockRonModel2
- fakeRonViewHolder = null
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+ fakeContractedRonViewHolder = KeepExistingView
+ fakeExpandedRonViewHolder = KeepExistingView
+ fakeHeadsUpRonViewHolder = KeepExistingView
+
+ inflateAndWait(notificationInflater, contentToInflate, row)
// Validate that for reinflation, the only thing we do us update the model
- verify(handle1, never()).dispose()
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel2 })
verify(privateLayout, never()).setContractedChild(any())
- verify(mockBinder1, never()).setupContentViewBinder()
- verify(handle1, never()).dispose()
+ verify(privateLayout, never()).setExpandedChild(any())
+ verify(privateLayout, never()).setHeadsUpChild(any())
+ verify(mockContractedViewBinder, never()).setupContentViewBinder()
+ verify(mockExpandedViewBinder, never()).setupContentViewBinder()
+ verify(mockHeadsUpViewBinder, never()).setupContentViewBinder()
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index dfee2ed43dc0..76dc65cbc915 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -17,17 +17,24 @@
package com.android.systemui.statusbar.phone
import android.app.AlarmManager
+import android.app.AutomaticZenRule
+import android.app.NotificationManager
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyResourcesManager
import android.content.SharedPreferences
+import android.net.Uri
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import android.service.notification.SystemZenRules
+import android.service.notification.ZenModeConfig
import android.telecom.TelecomManager
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -53,12 +60,13 @@ import com.android.systemui.statusbar.policy.RotationLockController
import com.android.systemui.statusbar.policy.SensorPrivacyController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.RingerModeTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.DateFormatUtil
import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -83,7 +91,10 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -91,7 +102,11 @@ import org.mockito.kotlin.argumentCaptor
@SmallTest
class PhoneStatusBarPolicyTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val zenModeRepository = kosmos.fakeZenModeRepository
+
companion object {
+ private const val ZEN_SLOT = "zen"
private const val ALARM_SLOT = "alarm"
private const val CAST_SLOT = "cast"
private const val SCREEN_RECORD_SLOT = "screen_record"
@@ -109,7 +124,6 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
@Mock private lateinit var userInfoController: UserInfoController
@Mock private lateinit var rotationLockController: RotationLockController
@Mock private lateinit var dataSaverController: DataSaverController
- @Mock private lateinit var zenModeController: ZenModeController
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var locationController: LocationController
@@ -133,6 +147,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
private val testScope = TestScope(UnconfinedTestDispatcher())
private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
+ private val zenModeController = FakeZenModeController()
private lateinit var executor: FakeExecutor
private lateinit var statusBarPolicy: PhoneStatusBarPolicy
@@ -374,6 +389,102 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
}
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun zenModeInteractorActiveModeChanged_showsModeIcon() =
+ testScope.runTest {
+ statusBarPolicy.init()
+ reset(iconController)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setId("bedtime")
+ .setName("Bedtime Mode")
+ .setType(AutomaticZenRule.TYPE_BEDTIME)
+ .setActive(true)
+ .setPackage("some.package")
+ .setIconResId(123)
+ .build(),
+ TestModeBuilder()
+ .setId("other")
+ .setName("Other Mode")
+ .setType(AutomaticZenRule.TYPE_OTHER)
+ .setActive(true)
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setIconResId(456)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
+ verify(iconController)
+ .setResourceIcon(
+ eq(ZEN_SLOT),
+ eq("some.package"),
+ eq(123),
+ eq(null),
+ eq("Bedtime Mode")
+ )
+
+ zenModeRepository.deactivateMode("bedtime")
+ runCurrent()
+
+ verify(iconController)
+ .setResourceIcon(eq(ZEN_SLOT), eq(null), eq(456), eq(null), eq("Other Mode"))
+
+ zenModeRepository.deactivateMode("other")
+ runCurrent()
+
+ verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun zenModeControllerOnGlobalZenChanged_doesNotUpdateDndIcon() {
+ statusBarPolicy.init()
+ reset(iconController)
+
+ zenModeController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null)
+
+ verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
+ verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
+ verify(iconController, never()).setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any())
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun zenModeInteractorActiveModeChanged_withFlagDisabled_ignored() =
+ testScope.runTest {
+ statusBarPolicy.init()
+ reset(iconController)
+
+ zenModeRepository.addMode(id = "Bedtime", active = true)
+ runCurrent()
+
+ verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
+ verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
+ verify(iconController, never())
+ .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any())
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun zenModeControllerOnGlobalZenChanged_withFlagDisabled_updatesDndIcon() {
+ statusBarPolicy.init()
+ reset(iconController)
+
+ zenModeController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null)
+
+ verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
+ verify(iconController).setIcon(eq(ZEN_SLOT), anyInt(), eq("Priority only"))
+
+ zenModeController.setZen(Settings.Global.ZEN_MODE_OFF, null, null)
+
+ verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
+ }
+
private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
return AlarmManager.AlarmClockInfo(10L, null)
}
@@ -412,6 +523,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
privacyItemController,
privacyLogger,
fakeConnectedDisplayStateProvider,
+ kosmos.zenModeInteractor,
JavaAdapter(testScope.backgroundScope)
)
}
@@ -433,4 +545,51 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() {
override val concurrentDisplaysInProgress: Flow<Boolean>
get() = TODO("Not yet implemented")
}
+
+ private class FakeZenModeController : ZenModeController {
+
+ private val callbacks = mutableListOf<ZenModeController.Callback>()
+ private var zen = Settings.Global.ZEN_MODE_OFF
+ private var consolidatedPolicy = NotificationManager.Policy(0, 0, 0)
+
+ override fun addCallback(listener: ZenModeController.Callback) {
+ callbacks.add(listener)
+ }
+
+ override fun removeCallback(listener: ZenModeController.Callback) {
+ callbacks.remove(listener)
+ }
+
+ override fun setZen(zen: Int, conditionId: Uri?, reason: String?) {
+ this.zen = zen
+ callbacks.forEach { it.onZenChanged(zen) }
+ }
+
+ override fun getZen(): Int = zen
+
+ override fun getManualRule(): ZenModeConfig.ZenRule = throw NotImplementedError()
+
+ override fun getConfig(): ZenModeConfig = throw NotImplementedError()
+
+ fun setConsolidatedPolicy(policy: NotificationManager.Policy) {
+ this.consolidatedPolicy = policy
+ callbacks.forEach { it.onConsolidatedPolicyChanged(consolidatedPolicy) }
+ }
+
+ override fun getConsolidatedPolicy(): NotificationManager.Policy = consolidatedPolicy
+
+ override fun getNextAlarm() = throw NotImplementedError()
+
+ override fun isZenAvailable() = throw NotImplementedError()
+
+ override fun getEffectsSuppressor() = throw NotImplementedError()
+
+ override fun isCountdownConditionSupported() = throw NotImplementedError()
+
+ override fun getCurrentUser() = throw NotImplementedError()
+
+ override fun isVolumeRestricted() = throw NotImplementedError()
+
+ override fun areNotificationsHiddenInShade() = throw NotImplementedError()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 9b611057c059..b75ac2bc9bde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -108,7 +108,9 @@ import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewMan
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.time.FakeSystemClock;
import com.google.common.truth.Truth;
@@ -175,6 +177,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mBouncerExpansionCallback;
private FakeKeyguardStateController mKeyguardStateController =
spy(new FakeKeyguardStateController());
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Mock
private ViewRootImpl mViewRootImpl;
@@ -238,6 +241,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mock(JavaAdapter.class),
() -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
+ mExecutor,
() -> mDeviceEntryInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
@@ -760,6 +764,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mock(JavaAdapter.class),
() -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
+ mExecutor,
() -> mDeviceEntryInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
@@ -1084,6 +1089,9 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
.thenReturn(KeyguardState.LOCKSCREEN);
reset(mCentralSurfaces);
+ // Advance past reattempts
+ mStatusBarKeyguardViewManager.setAttemptsToShowBouncer(10);
+
mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
verify(mPrimaryBouncerInteractor).show(true);
verify(mCentralSurfaces).showKeyguard();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
index 19abbd58ad3a..26a57e4c1ca9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.phone.ui
+import android.graphics.drawable.ColorDrawable
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.StatusBarIcon
@@ -406,6 +408,33 @@ class StatusBarIconControllerImplTest : SysuiTestCase() {
.isInstanceOf(StatusBarIconHolder.BindableIconHolder::class.java)
}
+ @Test
+ fun setIcon_setsIconInHolder() {
+ underTest.setIcon("slot", 123, "description")
+
+ val iconHolder = iconList.getIconHolder("slot", 0)
+ assertThat(iconHolder).isNotNull()
+ assertThat(iconHolder?.icon?.pkg).isEqualTo(mContext.packageName)
+ assertThat(iconHolder?.icon?.icon?.resId).isEqualTo(123)
+ assertThat(iconHolder?.icon?.icon?.resPackage).isEqualTo(mContext.packageName)
+ assertThat(iconHolder?.icon?.contentDescription).isEqualTo("description")
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS)
+ fun setResourceIcon_setsIconAndPreloadedIconInHolder() {
+ val drawable = ColorDrawable(1)
+ underTest.setResourceIcon("slot", "some.package", 123, drawable, "description")
+
+ val iconHolder = iconList.getIconHolder("slot", 0)
+ assertThat(iconHolder).isNotNull()
+ assertThat(iconHolder?.icon?.pkg).isEqualTo("some.package")
+ assertThat(iconHolder?.icon?.icon?.resId).isEqualTo(123)
+ assertThat(iconHolder?.icon?.icon?.resPackage).isEqualTo("some.package")
+ assertThat(iconHolder?.icon?.contentDescription).isEqualTo("description")
+ assertThat(iconHolder?.icon?.preloadedIcon).isEqualTo(drawable)
+ }
+
private fun createExternalIcon(): StatusBarIcon {
return StatusBarIcon(
"external.package",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index c3cc33ffb81a..bf31f1e6d569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -45,6 +45,7 @@ import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
@@ -88,7 +89,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
- fun icon_nullWhenShouldNotShow_satelliteNotAllowed() =
+ fun icon_null_satelliteNotAllowed() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -108,7 +109,30 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
- fun icon_nullWhenShouldNotShow_notAllOos() =
+ fun icon_null_connectedAndNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is not allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN satellite state is Connected. (this should not ever occur, but still)
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null despite the connected state
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_null_notAllOos() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -127,9 +151,28 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_nullWhenShouldNotShow_isEmergencyOnly() =
+ fun icon_null_allOosAndNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null because it is not allowed
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_null_isEmergencyOnly() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -158,7 +201,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
- fun icon_nullWhenShouldNotShow_apmIsEnabled() =
+ fun icon_null_apmIsEnabled() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -177,9 +220,8 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_satelliteIsOn() =
+ fun icon_notNull_satelliteAllowedAndAllOos() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -201,7 +243,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isInstanceOf(Icon::class.java)
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun icon_hysteresisWhenEnablingIcon() =
testScope.runTest {
@@ -234,9 +275,56 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_deviceIsProvisioned() =
+ fun icon_ignoresHysteresis_whenConnected() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // GIVEN satellite reports that it is Connected
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // THEN icon is non null because we are connected, despite the normal OOS icon waiting
+ // 10 seconds for hysteresis
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
+
+ @Test
+ fun icon_ignoresHysteresis_whenOn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // GIVEN satellite reports that it is Connected
+ repo.connectionState.value = SatelliteConnectionState.On
+
+ // THEN icon is non null because the connection state is On, despite the normal OOS icon
+ // waiting 10 seconds for hysteresis
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
+
+ @Test
+ fun icon_satelliteIsProvisioned() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -267,7 +355,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isInstanceOf(Icon::class.java)
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun icon_wifiIsActive() =
testScope.runTest {
@@ -324,13 +411,13 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
- fun carrierText_nullWhenShouldNotShow_notAllOos() =
+ fun carrierText_null_notAllOos() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
- // GIVEN satellite is allowed + connected
+ // GIVEN satellite is allowed + off
repo.isSatelliteAllowedForCurrentLocation.value = true
- repo.connectionState.value = SatelliteConnectionState.Connected
+ repo.connectionState.value = SatelliteConnectionState.Off
// GIVEN all icons are not OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
@@ -344,9 +431,8 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_nullWhenShouldNotShow_isEmergencyOnly() =
+ fun carrierText_notNull_notAllOos_butConnected() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -354,25 +440,17 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
repo.isSatelliteAllowedForCurrentLocation.value = true
repo.connectionState.value = SatelliteConnectionState.Connected
- // GIVEN all icons are OOS
+ // GIVEN all icons are not OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
- i1.isInService.value = false
+ i1.isInService.value = true
i1.isEmergencyOnly.value = false
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
- // Wait for delay to be completed
- advanceTimeBy(10.seconds)
-
- // THEN carrier text is set because we don't have service
+ // THEN carrier text is not null, because it is connected
+ // This case should never happen, but let's test it anyway
assertThat(latest).isNotNull()
-
- // GIVEN the connection is emergency only
- i1.isEmergencyOnly.value = true
-
- // THEN carrier text is null because we have emergency connection
- assertThat(latest).isNull()
}
@Test
@@ -396,7 +474,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_satelliteIsOn() =
testScope.runTest {
@@ -421,9 +498,8 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_hysteresisWhenEnablingText() =
+ fun carrierText_noHysteresisWhenEnablingText_connected() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -439,23 +515,10 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
- // THEN carrier text is null because of the hysteresis
- assertThat(latest).isNull()
-
- // Wait for delay to be completed
- advanceTimeBy(10.seconds)
-
- // THEN carrier text is set after the delay
+ // THEN carrier text is not null because we skip hysteresis when connected
assertThat(latest).isNotNull()
-
- // GIVEN apm is enabled
- airplaneModeRepository.setIsAirplaneMode(true)
-
- // THEN carrier text is null immediately
- assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_deviceIsProvisioned() =
testScope.runTest {
@@ -489,7 +552,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_wifiIsActive() =
testScope.runTest {
@@ -526,9 +588,8 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_connectionStateUnknown_null() =
+ fun carrierText_connectionStateUnknown_usesEmergencyOnlyText() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -544,12 +605,12 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// Wait for delay to be completed
advanceTimeBy(10.seconds)
- assertThat(latest).isNull()
+ assertThat(latest)
+ .isEqualTo(context.getString(R.string.satellite_emergency_only_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_connectionStateOff_null() =
+ fun carrierText_connectionStateOff_usesEmergencyOnlyText() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -565,10 +626,10 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// Wait for delay to be completed
advanceTimeBy(10.seconds)
- assertThat(latest).isNull()
+ assertThat(latest)
+ .isEqualTo(context.getString(R.string.satellite_emergency_only_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_connectionStateOn_notConnectedString() =
testScope.runTest {
@@ -590,7 +651,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
.isEqualTo(context.getString(R.string.satellite_connected_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_connectionStateConnected_connectedString() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
index 84cd79d9c8dc..25ceea951d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
@@ -26,8 +26,8 @@ class CreateUserActivityTest : SysuiTestCase() {
val dialog: Dialog = mock()
whenever(
createDialog(
- /* activity = */ nullable(),
- /* activityStarter = */ nullable(),
+ /* activity = */ any(),
+ /* activityStarter = */ any(),
/* isMultipleAdminsEnabled = */ any(),
/* successCallback = */ nullable(),
/* cancelCallback = */ nullable()
diff --git a/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
new file mode 100644
index 000000000000..6251ae909fc8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.statusBarManager by Kosmos.Fixture { mock<StatusBarManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index c00454f3bc48..5d7e7c726c6c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -30,7 +30,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
override fun addWidget(
provider: ComponentName,
user: UserHandle,
- priority: Int,
+ rank: Int?,
configurator: WidgetConfigurator?
) {
coroutineScope.launch {
@@ -38,7 +38,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
val providerInfo = AppWidgetProviderInfo().apply { this.provider = provider }
val configured = configurator?.configureWidget(id) ?: true
if (configured) {
- onConfigured(id, providerInfo, priority)
+ onConfigured(id, providerInfo, rank ?: -1)
}
}
}
@@ -46,14 +46,14 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
fun addWidget(
appWidgetId: Int,
componentName: String = "pkg/cls",
- priority: Int = 0,
+ rank: Int = 0,
category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
userId: Int = 0,
) {
fakeDatabase[appWidgetId] =
CommunalWidgetContentModel.Available(
appWidgetId = appWidgetId,
- priority = priority,
+ rank = rank,
providerInfo =
AppWidgetProviderInfo().apply {
provider = ComponentName.unflattenFromString(componentName)!!
@@ -73,14 +73,14 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
fun addPendingWidget(
appWidgetId: Int,
componentName: String = "pkg/cls",
- priority: Int = 0,
+ rank: Int = 0,
icon: Bitmap? = null,
userId: Int = 0,
) {
fakeDatabase[appWidgetId] =
CommunalWidgetContentModel.Pending(
appWidgetId = appWidgetId,
- priority = priority,
+ rank = rank,
componentName = ComponentName.unflattenFromString(componentName)!!,
icon = icon,
user = UserHandle(userId),
@@ -97,8 +97,8 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
override fun abortRestoreWidgets() {}
- private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
+ private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, rank: Int) {
_communalWidgets.value +=
- listOf(CommunalWidgetContentModel.Available(id, providerInfo, priority))
+ listOf(CommunalWidgetContentModel.Available(id, providerInfo, rank))
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index b9be04dc0a32..3dfe0eea500f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -33,6 +33,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +58,7 @@ val Kosmos.deviceEntryFaceAuthInteractor by
powerInteractor = powerInteractor,
biometricSettingsRepository = biometricSettingsRepository,
trustManager = trustManager,
+ sceneInteractor = { sceneInteractor },
deviceEntryFaceAuthStatusInteractor = deviceEntryFaceAuthStatusInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index cdfb297c27b9..fb4e2fb5fd14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.education.data.repository
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -27,22 +28,34 @@ class FakeContextualEducationRepository : ContextualEducationRepository {
private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
private val _gestureEduModels = MutableStateFlow(GestureEduModel(userId = 0))
private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+
+ private val userEduDeviceConnectionTimeMap = mutableMapOf<Int, EduDeviceConnectionTime>()
+ private val _eduDeviceConnectionTime = MutableStateFlow(EduDeviceConnectionTime())
+ private val eduDeviceConnectionTime = _eduDeviceConnectionTime.asStateFlow()
+
private var currentUser: Int = 0
override fun setUser(userId: Int) {
if (!userGestureMap.contains(userId)) {
userGestureMap[userId] = GestureEduModel(userId = userId)
+ userEduDeviceConnectionTimeMap[userId] = EduDeviceConnectionTime()
}
// save data of current user to the map
userGestureMap[currentUser] = _gestureEduModels.value
+ userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value
// switch to data of new user
_gestureEduModels.value = userGestureMap[userId]!!
+ _eduDeviceConnectionTime.value = userEduDeviceConnectionTimeMap[userId]!!
}
override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
return gestureEduModelsFlow
}
+ override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> {
+ return eduDeviceConnectionTime
+ }
+
override suspend fun updateGestureEduModel(
gestureType: GestureType,
transform: (GestureEduModel) -> GestureEduModel
@@ -50,4 +63,11 @@ class FakeContextualEducationRepository : ContextualEducationRepository {
val currentModel = _gestureEduModels.value
_gestureEduModels.value = transform(currentModel)
}
+
+ override suspend fun updateEduDeviceConnectionTime(
+ transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+ ) {
+ val currentModel = _eduDeviceConnectionTime.value
+ _eduDeviceConnectionTime.value = transform(currentModel)
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 5088677161d8..88ab170d72b3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -17,14 +17,26 @@
package com.android.systemui.education.domain.interactor
import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
+import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.touchpad.data.repository.touchpadRepository
+import com.android.systemui.user.data.repository.userRepository
var Kosmos.keyboardTouchpadEduInteractor by
Kosmos.Fixture {
KeyboardTouchpadEduInteractor(
backgroundScope = testScope.backgroundScope,
contextualEducationInteractor = contextualEducationInteractor,
+ userInputDeviceRepository =
+ UserInputDeviceRepository(
+ testDispatcher,
+ keyboardRepository,
+ touchpadRepository,
+ userRepository
+ ),
clock = fakeEduClock
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
new file mode 100644
index 000000000000..5ad973a54252
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl
+
+import com.google.android.msdl.data.model.FeedbackLevel
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.android.msdl.domain.MSDLPlayer
+
+class FakeMSDLPlayer : MSDLPlayer {
+ var currentFeedbackLevel = FeedbackLevel.DEFAULT
+ var latestTokenPlayed: MSDLToken? = null
+ private set
+
+ var latestPropertiesPlayed: InteractionProperties? = null
+ private set
+
+ override fun getSystemFeedbackLevel(): FeedbackLevel = currentFeedbackLevel
+
+ override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
+ latestTokenPlayed = token
+ latestPropertiesPlayed = properties
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
new file mode 100644
index 000000000000..f5a05b44d2cf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.msdlPlayer by Kosmos.Fixture { FakeMSDLPlayer() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
deleted file mode 100644
index 9b7bca6d2d34..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2023 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 android.content.Context
-import android.os.Handler
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.time.FakeSystemClock
-import kotlinx.coroutines.test.TestScope
-import org.mockito.Mockito.mock
-
-/**
- * Helper to create a new KeyguardDismissInteractor in a way that doesn't require modifying many
- * tests whenever we add a constructor param.
- */
-object KeyguardDismissInteractorFactory {
- @JvmOverloads
- @JvmStatic
- fun create(
- context: Context,
- testScope: TestScope,
- trustRepository: FakeTrustRepository = FakeTrustRepository(),
- keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
- bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
- keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java),
- powerRepository: FakePowerRepository = FakePowerRepository(),
- userRepository: FakeUserRepository = FakeUserRepository(),
- ): WithDependencies {
- val primaryBouncerInteractor =
- PrimaryBouncerInteractor(
- bouncerRepository,
- mock(BouncerView::class.java),
- mock(Handler::class.java),
- mock(KeyguardStateController::class.java),
- mock(KeyguardSecurityModel::class.java),
- mock(PrimaryBouncerCallbackInteractor::class.java),
- mock(FalsingCollector::class.java),
- mock(DismissCallbackRegistry::class.java),
- context,
- keyguardUpdateMonitor,
- trustRepository,
- testScope.backgroundScope,
- mock(SelectedUserInteractor::class.java),
- mock(DeviceEntryFaceAuthInteractor::class.java),
- )
- val alternateBouncerInteractor =
- AlternateBouncerInteractor(
- mock(StatusBarStateController::class.java),
- mock(KeyguardStateController::class.java),
- bouncerRepository,
- FakeFingerprintPropertyRepository(),
- FakeBiometricSettingsRepository(),
- FakeSystemClock(),
- keyguardUpdateMonitor,
- { mock(DeviceEntryBiometricsAllowedInteractor::class.java) },
- { mock(KeyguardInteractor::class.java) },
- { mock(KeyguardTransitionInteractor::class.java) },
- { mock(SceneInteractor::class.java) },
- testScope.backgroundScope,
- )
- val powerInteractorWithDeps =
- PowerInteractorFactory.create(
- repository = powerRepository,
- )
- val selectedUserInteractor = SelectedUserInteractor(repository = userRepository)
- return WithDependencies(
- trustRepository = trustRepository,
- keyguardRepository = keyguardRepository,
- bouncerRepository = bouncerRepository,
- keyguardUpdateMonitor = keyguardUpdateMonitor,
- powerRepository = powerRepository,
- userRepository = userRepository,
- interactor =
- KeyguardDismissInteractor(
- trustRepository,
- keyguardRepository,
- primaryBouncerInteractor,
- alternateBouncerInteractor,
- powerInteractorWithDeps.powerInteractor,
- selectedUserInteractor,
- ),
- )
- }
-
- data class WithDependencies(
- val trustRepository: FakeTrustRepository,
- val keyguardRepository: FakeKeyguardRepository,
- val bouncerRepository: FakeKeyguardBouncerRepository,
- val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- val powerRepository: FakePowerRepository,
- val userRepository: FakeUserRepository,
- val interactor: KeyguardDismissInteractor,
- )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
index f33ca95e488d..ace11573c7c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
@@ -20,7 +20,10 @@ import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -29,11 +32,14 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.keyguardDismissInteractor by
Kosmos.Fixture {
KeyguardDismissInteractor(
- trustRepository = trustRepository,
+ mainDispatcher = testDispatcher,
+ scope = applicationCoroutineScope,
keyguardRepository = keyguardRepository,
primaryBouncerInteractor = primaryBouncerInteractor,
+ selectedUserInteractor = selectedUserInteractor,
+ dismissCallbackRegistry = dismissCallbackRegistry,
+ trustRepository = trustRepository,
alternateBouncerInteractor = alternateBouncerInteractor,
powerInteractor = powerInteractor,
- selectedUserInteractor = selectedUserInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index a95609efc16f..f5232ce8a200 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -54,6 +54,7 @@ object KeyguardInteractorFactory {
sceneInteractor: SceneInteractor = mock(),
fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(),
fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor = mock(),
+ fromOccludedTransitionInteractor: FromOccludedTransitionInteractor = mock(),
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor? = null,
powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
testScope: CoroutineScope = TestScope(),
@@ -100,6 +101,7 @@ object KeyguardInteractorFactory {
sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
sharedNotificationContainerInteractor = { sncInteractor },
applicationScope = testScope,
),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 5ab56e931175..e85114d04825 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -38,6 +38,7 @@ val Kosmos.keyguardInteractor: KeyguardInteractor by
sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
sharedNotificationContainerInteractor = { sharedNotificationContainerInteractor },
applicationScope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 73799b63a6fc..769612c988ba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -20,6 +20,7 @@ import android.content.applicationContext
import android.view.accessibility.accessibilityManagerWrapper
import com.android.internal.logging.uiEventLogger
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
@@ -38,5 +39,6 @@ val Kosmos.keyguardTouchHandlingInteractor by
broadcastDispatcher = broadcastDispatcher,
accessibilityManager = accessibilityManagerWrapper,
pulsingGestureListener = pulsingGestureListener,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
index f1d87fe3abb7..29583153ccc6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
@@ -19,7 +19,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -33,6 +32,5 @@ val Kosmos.alternateBouncerViewModel by Fixture {
keyguardTransitionInteractor = keyguardTransitionInteractor,
dismissCallbackRegistry = dismissCallbackRegistry,
alternateBouncerInteractor = { alternateBouncerInteractor },
- primaryBouncerInteractor = primaryBouncerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 19b32bce77e7..f47b2df607c1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.biometrics.authController
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.kosmos.Kosmos
@@ -34,5 +35,6 @@ val Kosmos.lockscreenContentViewModel by
shadeInteractor = shadeInteractor,
unfoldTransitionInteractor = unfoldTransitionInteractor,
occlusionInteractor = sceneContainerOcclusionInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 9fe66eb77feb..953363d010fe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -42,6 +42,7 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromOccludedTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -128,6 +129,7 @@ class KosmosJavaAdapter() {
val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
val fromLockscreenTransitionInteractor by lazy { kosmos.fromLockscreenTransitionInteractor }
+ val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor }
val fromPrimaryBouncerTransitionInteractor by lazy {
kosmos.fromPrimaryBouncerTransitionInteractor
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
index 4c05939041bd..e66a2be66934 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
@@ -21,7 +21,7 @@ import kotlinx.coroutines.awaitCancellation
class FakeActivatable(
private val onActivation: () -> Unit = {},
private val onDeactivation: () -> Unit = {},
-) : BaseActivatable() {
+) : ExclusiveActivatable() {
var activationCount = 0
var cancellationCount = 0
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
index 90cd8c766da8..165246284b5f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.lifecycle
import androidx.compose.runtime.getValue
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,19 +28,21 @@ class FakeSysUiViewModel(
private val onDeactivation: () -> Unit = {},
private val upstreamFlow: Flow<Boolean> = flowOf(true),
private val upstreamStateFlow: StateFlow<Boolean> = MutableStateFlow(true).asStateFlow(),
-) : SysUiViewModel() {
+) : SysUiViewModel, ExclusiveActivatable() {
var activationCount = 0
var cancellationCount = 0
- val stateBackedByFlow: Boolean by hydratedStateOf(initialValue = true, source = upstreamFlow)
- val stateBackedByStateFlow: Boolean by hydratedStateOf(source = upstreamStateFlow)
+ private val hydrator = Hydrator()
+ val stateBackedByFlow: Boolean by
+ hydrator.hydratedStateOf(initialValue = true, source = upstreamFlow)
+ val stateBackedByStateFlow: Boolean by hydrator.hydratedStateOf(source = upstreamStateFlow)
override suspend fun onActivated(): Nothing {
activationCount++
onActivation()
try {
- awaitCancellation()
+ hydrator.activate()
} finally {
cancellationCount++
onDeactivation()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
index 373af5cdf4b7..373af5cdf4b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
index 147318473998..61d5f1e3af53 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
@@ -26,7 +26,7 @@ import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.media.controls.util.mediaUiEventLogger
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.notificationLockscreenUserManager
-import com.android.systemui.util.time.systemClock
+import com.android.systemui.util.time.fakeSystemClock
import com.android.systemui.util.wakelock.WakeLockFake
val Kosmos.mediaDataFilter by
@@ -42,7 +42,7 @@ val Kosmos.mediaDataFilter by
),
lockscreenUserManager = notificationLockscreenUserManager,
executor = fakeExecutor,
- systemClock = systemClock,
+ systemClock = fakeSystemClock,
logger = mediaUiEventLogger,
mediaFlags = mediaFlags,
mediaFilterRepository = mediaFilterRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
new file mode 100644
index 000000000000..a5690a0fa560
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.statusBarManager
+import android.content.testableContext
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.mediaDataLoader by
+ Kosmos.Fixture {
+ MediaDataLoader(
+ testableContext,
+ testDispatcher,
+ testScope,
+ activityStarter,
+ fakeMediaControllerFactory,
+ mediaFlags,
+ imageLoader,
+ statusBarManager
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index cc1ad1fda6dd..2127a88e5a45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -28,7 +28,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.media.controls.util.mediaUiEventLogger
import com.android.systemui.plugins.activityStarter
@@ -46,7 +46,7 @@ val Kosmos.mediaDataProcessor by
uiExecutor = fakeExecutor,
foregroundExecutor = fakeExecutor,
handler = fakeExecutorHandler,
- mediaControllerFactory = mediaControllerFactory,
+ mediaControllerFactory = fakeMediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
index b98f557c0c34..c479ce676761 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
@@ -22,8 +22,8 @@ import android.os.fakeExecutorHandler
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.localMediaManagerFactory
-import com.android.systemui.media.controls.util.mediaControllerFactory
import com.android.systemui.media.muteawait.mediaMuteAwaitConnectionManagerFactory
import com.android.systemui.statusbar.policy.configurationController
@@ -31,7 +31,7 @@ val Kosmos.mediaDeviceManager by
Kosmos.Fixture {
MediaDeviceManager(
context = applicationContext,
- controllerFactory = mediaControllerFactory,
+ controllerFactory = fakeMediaControllerFactory,
localMediaManagerFactory = localMediaManagerFactory,
mr2manager = { MediaRouter2Manager.getInstance(applicationContext) },
muteAwaitConnectionManagerFactory = mediaMuteAwaitConnectionManagerFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
index 6ec6378e3bc2..b7660e05ee91 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
@@ -19,7 +19,7 @@ package com.android.systemui.media.controls.domain.pipeline
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.util.time.systemClock
@@ -27,7 +27,7 @@ import com.android.systemui.util.time.systemClock
val Kosmos.mediaTimeoutListener by
Kosmos.Fixture {
MediaTimeoutListener(
- mediaControllerFactory = mediaControllerFactory,
+ mediaControllerFactory = fakeMediaControllerFactory,
mainExecutor = fakeExecutor,
logger = MediaTimeoutLogger(logcatLogBuffer("MediaTimeoutLogBuffer")),
statusBarStateController = statusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
new file mode 100644
index 000000000000..7f8348e2ca6f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.MediaSession.Token
+
+class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
+
+ private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+
+ override fun create(token: MediaSession.Token): android.media.session.MediaController {
+ if (token !in mediaControllersForToken) {
+ super.create(token)
+ }
+ return mediaControllersForToken[token]!!
+ }
+
+ fun setControllerForToken(token: Token, mediaController: MediaController) {
+ mediaControllersForToken[token] = mediaController
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
index 1ce6e82f71d8..7ee58fa8a295 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
@@ -19,4 +19,5 @@ package com.android.systemui.media.controls.util
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
-val Kosmos.mediaControllerFactory by Kosmos.Fixture { MediaControllerFactory(applicationContext) }
+val Kosmos.fakeMediaControllerFactory by
+ Kosmos.Fixture { FakeMediaControllerFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index ae8b411a4b95..f84c3bdfdaf1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -24,7 +24,7 @@ import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.domain.resolver.sceneFamilyResolvers
import com.android.systemui.scene.shared.logger.sceneLogger
-val Kosmos.sceneInteractor by
+val Kosmos.sceneInteractor: SceneInteractor by
Kosmos.Fixture {
SceneInteractor(
applicationScope = applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
index 64e3526603f9..78358f5a9187 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
@@ -19,6 +19,8 @@ package com.android.systemui.scene.shared.model
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
@@ -26,7 +28,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
class FakeScene(
override val key: SceneKey,
-) : Scene {
+) : ExclusiveActivatable(), Scene {
var isDestinationScenesBeingCollected = false
private val destinationScenesChannel = Channel<Map<UserAction, UserActionResult>>()
@@ -37,6 +39,10 @@ class FakeScene(
.onStart { isDestinationScenesBeingCollected = true }
.onCompletion { isDestinationScenesBeingCollected = false }
+ override suspend fun onActivated(): Nothing {
+ awaitCancellation()
+ }
+
suspend fun setDestinationScenes(value: Map<UserAction, UserActionResult>) {
destinationScenesChannel.send(value)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
new file mode 100644
index 000000000000..7e511355372b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
+
+fun Kosmos.getEnRouteViewModel(repository: NotificationRowRepository) =
+ EnRouteViewModel(
+ dumpManager = dumpManager,
+ rowInteractor = getNotificationRowInteractor(repository),
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/ReferenceTestUtils.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/leak/ReferenceTestUtils.java
index b433e7a191f9..b433e7a191f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/ReferenceTestUtils.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/leak/ReferenceTestUtils.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index a8328e4ab991..2dbac670b298 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -14,8 +14,11 @@
package com.android.systemui.utils.leaks;
+import android.graphics.drawable.Drawable;
import android.testing.LeakCheck;
+import androidx.annotation.Nullable;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.ui.IconManager;
@@ -60,6 +63,11 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager>
}
@Override
+ public void setResourceIcon(String slot, @Nullable String resPackage, int iconResId,
+ @Nullable Drawable preloadedIcon, CharSequence contentDescription) {
+ }
+
+ @Override
public void setNewWifiIcon() {
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 09068d5e2b56..26b0f617d971 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -2605,11 +2605,7 @@ public class CameraExtensionsProxyService extends Service {
ret.size.height = imageReaderOutputConfig.getSize().getHeight();
ret.imageFormat = imageReaderOutputConfig.getImageFormat();
ret.capacity = imageReaderOutputConfig.getMaxImages();
- if (EFV_SUPPORTED) {
- ret.usage = imageReaderOutputConfig.getUsage();
- } else {
- ret.usage = 0;
- }
+ ret.usage = imageReaderOutputConfig.getUsage();
} else if (output instanceof MultiResolutionImageReaderOutputConfigImpl) {
MultiResolutionImageReaderOutputConfigImpl multiResReaderConfig =
(MultiResolutionImageReaderOutputConfigImpl) output;
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 58cd2e4cee6c..be4cd761a4ec 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -211,6 +211,7 @@ java_library {
libs: [
"junit",
"flag-junit",
+ "framework-annotations-lib",
],
visibility: ["//visibility:public"],
}
@@ -331,6 +332,7 @@ android_ravenwood_libgroup {
name: "ravenwood-runtime",
data: [
"framework-res",
+ "ravenwood-empty-res",
],
libs: [
"100-framework-minus-apex.ravenwood",
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index fbf27fa5bf4b..7e2ee3e2a052 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -12,9 +12,6 @@
{
"name": "RavenwoodBivalentTest_device"
},
- {
- "name": "RavenwoodResApkTest"
- },
// The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
{
"name": "SystemUIGoogleTests",
@@ -55,7 +52,7 @@
"host": true
},
{
- "name": "RavenwoodCoreTest",
+ "name": "RavenwoodResApkTest",
"host": true
},
{
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/bivalenttest/Android.bp
index 06cf08e6c3df..e897735493a3 100644
--- a/ravenwood/bivalenttest/Android.bp
+++ b/ravenwood/bivalenttest/Android.bp
@@ -39,6 +39,9 @@ android_ravenwood_test {
"androidx.test.ext.junit",
"androidx.test.rules",
+ "junit-params",
+ "platform-parametric-runner-lib",
+
// To make sure it won't cause VerifyError (b/324063814)
"platformprotosnano",
],
@@ -65,6 +68,9 @@ android_test {
"androidx.test.ext.junit",
"androidx.test.rules",
+ "junit-params",
+ "platform-parametric-runner-lib",
+
"ravenwood-junit",
],
jni_libs: [
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
new file mode 100644
index 000000000000..8dadd398ad55
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.fail;
+
+import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
+
+import android.util.Log;
+
+import java.lang.StackWalker.StackFrame;
+import java.util.HashMap;
+
+/**
+ * Used to keep track of and count the number of calls.
+ */
+public class CallTracker {
+ public static final String TAG = "CallTracker";
+
+ private final HashMap<String, Integer> mNumCalled = new HashMap<>();
+
+ /**
+ * Call it when a method is called. It increments the count for the calling method.
+ */
+ public void incrementMethodCallCount() {
+ var methodName = getCallingMethodName(1);
+
+ Log.i(TAG, "Method called: " + methodName);
+
+ mNumCalled.put(methodName, getNumCalled(methodName) + 1);
+ }
+
+ /**
+ * Return the number of calls of a method.
+ */
+ public int getNumCalled(String methodName) {
+ return mNumCalled.getOrDefault(methodName, 0);
+ }
+
+ /**
+ * Return the current method name. (with the class name.)
+ */
+ private static String getCallingMethodName(int frameOffset) {
+ var walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
+ var caller = walker.walk(frames ->
+ frames.skip(1 + frameOffset).findFirst().map(StackFrame::getMethodName)
+ );
+ return caller.get();
+ }
+
+ /**
+ * Check the number of calls stored in {@link #mNumCalled}.
+ */
+ protected void assertCalls(Object... methodNameAndCountPairs) {
+ // Create a local copy
+ HashMap<String, Integer> counts = new HashMap<>(mNumCalled);
+ for (int i = 0; i < methodNameAndCountPairs.length - 1; i += 2) {
+ String methodName = (String) methodNameAndCountPairs[i];
+ int expectedCount = (Integer) methodNameAndCountPairs[i + 1];
+
+ if (getNumCalled(methodName) != expectedCount) {
+ fail(String.format("Method %s: expected call count=%d, actual=%d",
+ methodName, expectedCount, getNumCalled(methodName)));
+ }
+ counts.remove(methodName);
+ }
+ // All other entries are expected to be 0.
+ var sb = new StringBuilder();
+ for (var e : counts.entrySet()) {
+ if (e.getValue() == 0) {
+ continue;
+ }
+ sb.append(String.format("Method %s: expected call count=0, actual=%d",
+ e.getKey(), e.getValue()));
+ }
+ if (sb.length() > 0) {
+ fail(sb.toString());
+ }
+ }
+
+ /**
+ * Same as {@link #assertCalls(Object...)} but it kills the process if it fails.
+ * Only use in @AfterClass.
+ */
+ protected void assertCallsOrDie(Object... methodNameAndCountPairs) {
+ try {
+ assertCalls(methodNameAndCountPairs);
+ } catch (Throwable th) {
+ // TODO: I don't think it's by spec, but the exception here would be ignored both on
+ // ravenwood and on the device side. Look into it.
+ Log.e(TAG, "*** Failure detected in @AfterClass! ***", th);
+ Log.e(TAG, "JUnit seems to ignore exceptions from @AfterClass, so killing self.");
+ System.exit(7);
+ }
+ }
+
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
new file mode 100644
index 000000000000..d7c2c6cd73a8
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.assertFalse;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure RavenwoodAwareTestRunnerTest properly delegates to the original runner,
+ * and also run the special annotated methods.
+ */
+@RunWith(JUnitParamsRunner.class)
+public class RavenwoodAwareTestRunnerTest {
+ public static final String TAG = "RavenwoodAwareTestRunnerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ private static int getExpectedRavenwoodRunnerInitializingNumCalls() {
+ return RavenwoodRule.isOnRavenwood() ? 1 : 0;
+ }
+
+ @RavenwoodTestRunnerInitializing
+ public static void ravenwoodRunnerInitializing() {
+ // No other calls should have been made.
+ sCallTracker.assertCalls();
+
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ sCallTracker.assertCalls(
+ "ravenwoodRunnerInitializing",
+ getExpectedRavenwoodRunnerInitializingNumCalls()
+ );
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ public void test1() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ @Parameters({"foo", "bar"})
+ public void testWithParams(String arg) {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ @DisabledOnRavenwood
+ public void testDeviceOnly() {
+ assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "ravenwoodRunnerInitializing",
+ getExpectedRavenwoodRunnerInitializingNumCalls(),
+ "beforeClass", 1,
+ "test1", 1,
+ "testWithParams", 2
+ );
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
new file mode 100644
index 000000000000..0f8be0eeebeb
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood
+public class RavenwoodImplicitClassRuleDeviceOnlyTest {
+ public static final String TAG = "RavenwoodImplicitClassRuleDeviceOnlyTest";
+
+ @BeforeClass
+ public static void beforeClass() {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+
+ @Test
+ public void testDeviceOnly() {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ if (RavenwoodRule.isOnRavenwood()) {
+ Log.e(TAG, "Even @AfterClass shouldn't be executed!");
+ System.exit(1);
+ }
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
new file mode 100644
index 000000000000..7ef40dc49e1a
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assume;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+
+/**
+ * Make sure ravenizer will inject implicit rules and rewrite the existing rules' orders.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodImplicitRuleOrderRewriteTest {
+
+ private static final TestRule sEmptyRule = (statement, description) -> statement;
+
+ // We have two sets of 9 rules below, for class rules and instance rules.
+ // - Ravenizer will inject 2 more rules of each kind.
+ // - Ravenizer will adjust their order, so even though we'll add two sets of class and instance
+ // rules with a MIN / MAX order, there will still be no duplicate in the order.
+
+ private static final int EXPECTED_RULE_COUNT = 9 + 2;
+
+ @ClassRule(order = Integer.MIN_VALUE)
+ public static final TestRule sRule01 = sEmptyRule;
+
+ @ClassRule(order = Integer.MIN_VALUE + 1)
+ public static final TestRule sRule02 = sEmptyRule;
+
+ @ClassRule(order = -10)
+ public static final TestRule sRule03 = sEmptyRule;
+
+ @ClassRule(order = -1)
+ public static final TestRule sRule04 = sEmptyRule;
+
+ @ClassRule(order = 0)
+ public static final TestRule sRule05 = sEmptyRule;
+
+ @ClassRule(order = 1)
+ public static final TestRule sRule06 = sEmptyRule;
+
+ @ClassRule(order = 10)
+ public static final TestRule sRule07 = sEmptyRule;
+
+ @ClassRule(order = Integer.MAX_VALUE - 1)
+ public static final TestRule sRule08 = sEmptyRule;
+
+ @ClassRule(order = Integer.MAX_VALUE)
+ public static final TestRule sRule09 = sEmptyRule;
+
+ @Rule(order = Integer.MIN_VALUE)
+ public final TestRule mRule01 = sEmptyRule;
+
+ @Rule(order = Integer.MIN_VALUE + 1)
+ public final TestRule mRule02 = sEmptyRule;
+
+ @Rule(order = -10)
+ public final TestRule mRule03 = sEmptyRule;
+
+ @Rule(order = -1)
+ public final TestRule mRule04 = sEmptyRule;
+
+ @Rule(order = 0)
+ public final TestRule mRule05 = sEmptyRule;
+
+ @Rule(order = 1)
+ public final TestRule mRule06 = sEmptyRule;
+
+ @Rule(order = 10)
+ public final TestRule mRule07 = sEmptyRule;
+
+ @Rule(order = Integer.MAX_VALUE - 1)
+ public final TestRule mRule08 = sEmptyRule;
+
+ @Rule(order = Integer.MAX_VALUE)
+ public final TestRule mRule09 = sEmptyRule;
+
+ private void checkRules(boolean classRule) {
+ final var anotClass = classRule ? ClassRule.class : Rule.class;
+
+ final HashMap<Integer, Integer> ordersUsed = new HashMap<>();
+
+ for (var field : this.getClass().getDeclaredFields()) {
+ if (!field.isAnnotationPresent(anotClass)) {
+ continue;
+ }
+ final var anot = field.getAnnotation(anotClass);
+ final int order = classRule ? ((ClassRule) anot).order() : ((Rule) anot).order();
+
+ if (ordersUsed.containsKey(order)) {
+ fail("Detected duplicate order=" + order);
+ }
+ ordersUsed.put(order, 1);
+ }
+ assertEquals(EXPECTED_RULE_COUNT, ordersUsed.size());
+ }
+
+ @Test
+ public void testClassRules() {
+ Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+
+ checkRules(true);
+ }
+
+ @Test
+ public void testInstanceRules() {
+ Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+
+ checkRules(false);
+ }
+}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
index d952d07b3817..ae596b10848b 100644
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
@@ -13,44 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwoodtest.coretest.methodvalidation;
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
-import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
+ * Test to make sure when a test class inherits another test class, the base class's
+ * implicit rules are shadowed and won't be executed.
+ *
+ * ... But for now, we don't have a way to programmatically check it, so for now we need to
+ * check the log file manually.
+ *
+ * TODO: Implement the test.
*/
@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_OkTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Before
- public void setUp() {
- }
-
- @Before
- public void testSetUp() {
- }
-
- @After
- public void tearDown() {
- }
-
- @After
- public void testTearDown() {
- }
-
+public class RavenwoodImplicitRuleShadowingTest extends RavenwoodImplicitRuleShadowingTestBase {
@Test
- public void testEmpty() {
+ public void testOkInSubClass() {
}
}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
index 5a3589dae43a..1ca97af632dd 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
@@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
-package com.android.platform.test.ravenwood.nativesubstitution;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.ravenwood.common.JvmWorkaround;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-import java.io.FileDescriptor;
-
-public class ParcelFileDescriptor_host {
- public static void setFdInt(FileDescriptor fd, int fdInt) {
- JvmWorkaround.getInstance().setFdInt(fd, fdInt);
- }
-
- public static int getFdInt(FileDescriptor fd) {
- return JvmWorkaround.getInstance().getFdInt(fd);
+/**
+ * A test class that's just inherited by RavenwoodImplicitRuleShadowingTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public abstract class RavenwoodImplicitRuleShadowingTestBase {
+ @Test
+ public void testOkInBaseClass() {
}
}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
new file mode 100644
index 000000000000..c042eb010558
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure ravenwood's test runner works with {@link AndroidJUnit4}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRunnerWithAndroidXRunnerTest {
+ public static final String TAG = "RavenwoodRunnerWithAndroidXRunnerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ @BeforeClass
+ public static void beforeClass() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Before
+ public void beforeTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @After
+ public void afterTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ public void test1() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ public void test2() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "beforeClass", 1,
+ "beforeTest", 2,
+ "afterTest", 2,
+ "test1", 1,
+ "test2", 1
+ );
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
new file mode 100644
index 000000000000..2feb5ba9aa01
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure ravenwood's test runner works with {@link AndroidJUnit4}.
+ */
+@RunWith(JUnitParamsRunner.class)
+public class RavenwoodRunnerWithJUnitParamsRunnerTest {
+ public static final String TAG = "RavenwoodRunnerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ @BeforeClass
+ public static void beforeClass() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Before
+ public void beforeTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @After
+ public void afterTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ public void testWithNoParams() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Test
+ @Parameters({"foo", "bar"})
+ public void testWithParams(String arg) {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "beforeClass", 1,
+ "beforeTest", 3,
+ "afterTest", 3,
+ "testWithNoParams", 1,
+ "testWithParams", 2
+ );
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
new file mode 100644
index 000000000000..7e3bc0fccd7f
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Make sure ravenwood's test runner works with {@link ParameterizedAndroidJunit4}.
+ */
+@RunWith(ParameterizedAndroidJunit4.class)
+public class RavenwoodRunnerWithParameterizedAndroidJunit4Test {
+ public static final String TAG = "RavenwoodRunnerTest";
+
+ private static final CallTracker sCallTracker = new CallTracker();
+
+ private final String mParam;
+
+ private static int sNumInsantiation = 0;
+
+ public RavenwoodRunnerWithParameterizedAndroidJunit4Test(String param) {
+ mParam = param;
+ sNumInsantiation++;
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ // It seems like ParameterizedAndroidJunit4 calls the @BeforeTest / @AfterTest methods
+ // one time too many.
+ // With two parameters, this method should be called only twice, but it's actually
+ // called three times.
+ // So let's not check the number fo beforeClass calls.
+ }
+
+ @Before
+ public void beforeTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @After
+ public void afterTest() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @Parameters
+ public static List<String> getParams() {
+ var params = new ArrayList<String>();
+ params.add("foo");
+ params.add("bar");
+ return params;
+ }
+
+ @Test
+ public void testWithParams() {
+ sCallTracker.incrementMethodCallCount();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.i(TAG, "afterClass called");
+
+ sCallTracker.assertCallsOrDie(
+ "beforeTest", sNumInsantiation,
+ "afterTest", sNumInsantiation,
+ "testWithParams", sNumInsantiation
+ );
+ }
+}
diff --git a/ravenwood/coretest/README.md b/ravenwood/coretest/README.md
deleted file mode 100644
index b60bfbfcb6f4..000000000000
--- a/ravenwood/coretest/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Ravenwood core test
-
-This test contains (non-bivalent) tests for Ravenwood itself -- e.g. tests for the ravenwood rules. \ No newline at end of file
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
deleted file mode 100644
index f1e33cb686f1..000000000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.coretest;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4; // Intentionally use the deprecated one.
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * Test for the test runner validator in RavenwoodRule.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestRunnerValidationTest {
- // Note the following rules don't have a @Rule, because they need to be applied in a specific
- // order. So we use a RuleChain instead.
- private ExpectedException mThrown = ExpectedException.none();
- private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
- public RavenwoodTestRunnerValidationTest() {
- Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled());
- // Because RavenwoodRule will throw this error before executing the test method,
- // we can't do it in the test method itself.
- // So instead, we initialize it here.
- mThrown.expectMessage("Switch to androidx.test.ext.junit.runners.AndroidJUnit4");
- }
-
- @Test
- public void testValidateTestRunner() {
- }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java
deleted file mode 100644
index db95fad2a3ad..000000000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail01_Test {
- private ExpectedException mThrown = ExpectedException.none();
- private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
- public RavenwoodTestMethodValidation_Fail01_Test() {
- mThrown.expectMessage("Method setUp() doesn't have @Before");
- }
-
- @SuppressWarnings("JUnit4SetUpNotRun")
- public void setUp() {
- }
-
- @Test
- public void testEmpty() {
- }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java
deleted file mode 100644
index ddc66c73a7c0..000000000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail02_Test {
- private ExpectedException mThrown = ExpectedException.none();
- private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
- public RavenwoodTestMethodValidation_Fail02_Test() {
- mThrown.expectMessage("Method tearDown() doesn't have @After");
- }
-
- @SuppressWarnings("JUnit4TearDownNotRun")
- public void tearDown() {
- }
-
- @Test
- public void testEmpty() {
- }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java
deleted file mode 100644
index ec8e907dcdb3..000000000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail03_Test {
- private ExpectedException mThrown = ExpectedException.none();
- private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
- public RavenwoodTestMethodValidation_Fail03_Test() {
- mThrown.expectMessage("Method testFoo() doesn't have @Test");
- }
-
- @SuppressWarnings("JUnit4TestNotRun")
- public void testFoo() {
- }
-
- @Test
- public void testEmpty() {
- }
-}
diff --git a/ravenwood/empty-res/Android.bp b/ravenwood/empty-res/Android.bp
new file mode 100644
index 000000000000..3af769067d35
--- /dev/null
+++ b/ravenwood/empty-res/Android.bp
@@ -0,0 +1,4 @@
+android_app {
+ name: "ravenwood-empty-res",
+ sdk_version: "current",
+}
diff --git a/ravenwood/empty-res/AndroidManifest.xml b/ravenwood/empty-res/AndroidManifest.xml
new file mode 100644
index 000000000000..f73460b42213
--- /dev/null
+++ b/ravenwood/empty-res/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.ravenwood.emptyres">
+</manifest>
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
new file mode 100644
index 000000000000..03600ad5511f
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.ravenwood;
+
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
+
+import android.os.Bundle;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Provide hook points created by {@link RavenwoodAwareTestRunner}.
+ */
+public class RavenwoodAwareTestRunnerHook {
+ private static final String TAG = "RavenwoodAwareTestRunnerHook";
+
+ private RavenwoodAwareTestRunnerHook() {
+ }
+
+ private static void log(String message) {
+ RavenwoodCommonUtils.log(TAG, message);
+ }
+
+ public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+ log("onRunnerStart: testClass=" + testClass + " runner=" + runner);
+
+ // TODO: Move the initialization code to a better place.
+
+ // This will let AndroidJUnit4 use the original runner.
+ System.setProperty("android.junit.runner",
+ "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
+ System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
+
+ // This is needed to make AndroidJUnit4ClassRunner happy.
+ InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+ }
+
+ public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
+ Scope scope, Order order) {
+ log("onBefore: description=" + description + ", " + scope + ", " + order);
+
+ // Class-level annotations are checked by the runner already, so we only check
+ // method-level annotations here.
+ if (scope == Scope.Instance && order == Order.First) {
+ if (!RavenwoodRule.shouldEnableOnRavenwood(description)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static void onAfter(RavenwoodAwareTestRunner runner, Description description,
+ Scope scope, Order order, Throwable th) {
+ log("onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 3ea4cb7fb69f..7b4c17390942 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,9 +16,9 @@
package android.platform.test.ravenwood;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -39,24 +39,12 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.os.RuntimeInit;
import com.android.server.LocalServices;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
import org.junit.runner.Description;
-import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executors;
@@ -109,10 +97,6 @@ public class RavenwoodRuleImpl {
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
-// android.os.SystemProperties.init$ravenwood(
-// rule.mSystemProperties.getValues(),
-// rule.mSystemProperties.getKeyReadablePredicate(),
-// rule.mSystemProperties.getKeyWritablePredicate());
setSystemProperties(rule.mSystemProperties);
ServiceManager.init$ravenwood();
@@ -131,11 +115,12 @@ public class RavenwoodRuleImpl {
// TODO This should be integrated into LoadedApk
final Supplier<Resources> resourcesSupplier = () -> {
- final var resApkFile = new File(RAVENWOOD_RESOURCE_APK).getAbsoluteFile();
+ var resApkFile = new File(RAVENWOOD_RESOURCE_APK);
+ if (!resApkFile.isFile()) {
+ resApkFile = new File(RAVENWOOD_EMPTY_RESOURCES_APK);
+ }
assertTrue(resApkFile.isFile());
-
- final var res = resApkFile.getAbsolutePath();
-
+ final String res = resApkFile.getAbsolutePath();
final var emptyPaths = new String[0];
ResourcesManager.getInstance().initializeApplicationPaths(res, emptyPaths);
@@ -243,102 +228,7 @@ public class RavenwoodRuleImpl {
public static void validate(Statement base, Description description,
boolean enableOptionalValidation) {
- validateTestRunner(base, description, enableOptionalValidation);
- validateTestAnnotations(base, description, enableOptionalValidation);
- }
-
- private static void validateTestRunner(Statement base, Description description,
- boolean shouldFail) {
- final var testClass = description.getTestClass();
- final var runWith = testClass.getAnnotation(RunWith.class);
- if (runWith == null) {
- return;
- }
-
- // Due to build dependencies, we can't directly refer to androidx classes here,
- // so just check the class name instead.
- if (runWith.value().getCanonicalName().equals("androidx.test.runner.AndroidJUnit4")) {
- var message = "Test " + testClass.getCanonicalName() + " uses deprecated"
- + " test runner androidx.test.runner.AndroidJUnit4."
- + " Switch to androidx.test.ext.junit.runners.AndroidJUnit4.";
- if (shouldFail) {
- Assert.fail(message);
- } else {
- System.err.println("Warning: " + message);
- }
- }
- }
-
- /**
- * @return if a method has any of annotations.
- */
- private static boolean hasAnyAnnotations(Method m, Class<? extends Annotation>... annotations) {
- for (var anno : annotations) {
- if (m.getAnnotation(anno) != null) {
- return true;
- }
- }
- return false;
- }
-
- private static void validateTestAnnotations(Statement base, Description description,
- boolean enableOptionalValidation) {
- final var testClass = description.getTestClass();
-
- final var message = new StringBuilder();
-
- boolean hasErrors = false;
- for (Method m : collectMethods(testClass)) {
- if (Modifier.isPublic(m.getModifiers()) && m.getName().startsWith("test")) {
- if (!hasAnyAnnotations(m, Test.class, Before.class, After.class,
- BeforeClass.class, AfterClass.class)) {
- message.append("\nMethod " + m.getName() + "() doesn't have @Test");
- hasErrors = true;
- }
- }
- if ("setUp".equals(m.getName())) {
- if (!hasAnyAnnotations(m, Before.class)) {
- message.append("\nMethod " + m.getName() + "() doesn't have @Before");
- hasErrors = true;
- }
- if (!Modifier.isPublic(m.getModifiers())) {
- message.append("\nMethod " + m.getName() + "() must be public");
- hasErrors = true;
- }
- }
- if ("tearDown".equals(m.getName())) {
- if (!hasAnyAnnotations(m, After.class)) {
- message.append("\nMethod " + m.getName() + "() doesn't have @After");
- hasErrors = true;
- }
- if (!Modifier.isPublic(m.getModifiers())) {
- message.append("\nMethod " + m.getName() + "() must be public");
- hasErrors = true;
- }
- }
- }
- assertFalse("Problem(s) detected in class " + testClass.getCanonicalName() + ":"
- + message, hasErrors);
- }
-
- /**
- * Collect all (public or private or any) methods in a class, including inherited methods.
- */
- private static List<Method> collectMethods(Class<?> clazz) {
- var ret = new ArrayList<Method>();
- collectMethods(clazz, ret);
- return ret;
- }
-
- private static void collectMethods(Class<?> clazz, List<Method> result) {
- // Class.getMethods() only return public methods, so we need to use getDeclaredMethods()
- // instead, and recurse.
- for (var m : clazz.getDeclaredMethods()) {
- result.add(m);
- }
- if (clazz.getSuperclass() != null) {
- collectMethods(clazz.getSuperclass(), result);
- }
+ // Nothing to check, for now.
}
/**
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
new file mode 100644
index 000000000000..a4fa41af26e5
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.ravenwood;
+
+import static android.platform.test.ravenwood.RavenwoodRule.shouldRunCassOnRavenwood;
+
+import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+import com.android.ravenwood.common.SneakyThrow;
+
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.InvalidOrderingException;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.manipulation.Orderable;
+import org.junit.runner.manipulation.Orderer;
+import org.junit.runner.manipulation.Sortable;
+import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A test runner used for Ravenwood.
+ *
+ * TODO: Handle ENABLE_PROBE_IGNORED
+ *
+ * It will delegate to another runner specified with {@link InnerRunner}
+ * (default = {@link BlockJUnit4ClassRunner}) with the following features.
+ * - Add a {@link RavenwoodAwareTestRunnerHook#onRunnerInitializing} hook, which is called before
+ * the inner runner gets a chance to run. This can be used to initialize stuff used by the
+ * inner runner.
+ * - Add hook points, which are handed by RavenwoodAwareTestRunnerHook, with help from
+ * the four test rules such as {@link #sImplicitClassMinRule}, which are also injected by
+ * the ravenizer tool.
+ *
+ * We use this runner to:
+ * - Initialize the bare minimum environmnet just to be enough to make the actual test runners
+ * happy.
+ * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
+ *
+ * This class is built such that it can also be used on a real device, but in that case
+ * it will basically just delegate to the inner wrapper, and won't do anything special.
+ * (no hooks, etc.)
+ */
+public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable {
+ private static final String TAG = "RavenwoodAwareTestRunner";
+
+ @Inherited
+ @Target({TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface InnerRunner {
+ Class<? extends Runner> value();
+ }
+
+ /**
+ * An annotation similar to JUnit's BeforeClass, but this gets executed before
+ * the inner runner is instantiated, and only on Ravenwood.
+ * It can be used to initialize what's needed by the inner runner.
+ */
+ @Target({METHOD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface RavenwoodTestRunnerInitializing {
+ }
+
+ /** Scope of a hook. */
+ public enum Scope {
+ Runner,
+ Class,
+ Instance,
+ }
+
+ /** Order of a hook. */
+ public enum Order {
+ First,
+ Last,
+ }
+
+ // The following four rule instances will be injected to tests by the Ravenizer tool.
+
+ public static final TestRule sImplicitClassMinRule = (base, description) ->
+ getCurrentRunner().updateStatement(base, description, Scope.Class, Order.First);
+
+ public static final TestRule sImplicitClassMaxRule = (base, description) ->
+ getCurrentRunner().updateStatement(base, description, Scope.Class, Order.Last);
+
+ public static final TestRule sImplicitInstMinRule = (base, description) ->
+ getCurrentRunner().updateStatement(base, description, Scope.Instance, Order.First);
+
+ public static final TestRule sImplicitInstMaxRule = (base, description) ->
+ getCurrentRunner().updateStatement(base, description, Scope.Instance, Order.Last);
+
+ public static final String IMPLICIT_CLASS_MIN_RULE_NAME = "sImplicitClassMinRule";
+ public static final String IMPLICIT_CLASS_MAX_RULE_NAME = "sImplicitClassMaxRule";
+ public static final String IMPLICIT_INST_MIN_RULE_NAME = "sImplicitInstMinRule";
+ public static final String IMPLICIT_INST_MAX_RULE_NAME = "sImplicitInstMaxRule";
+
+ /** Keeps track of the runner on the current thread. */
+ private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
+
+ private static RavenwoodAwareTestRunner getCurrentRunner() {
+ var runner = sCurrentRunner.get();
+ if (runner == null) {
+ throw new RuntimeException("Current test runner not set!");
+ }
+ return runner;
+ }
+
+ private final TestClass mTestClsas;
+ private final Runner mRealRunner;
+
+ /** Simple logging method. */
+ private void log(String message) {
+ RavenwoodCommonUtils.log(TAG, "[" + getTestClass() + " @" + this + "] " + message);
+ }
+
+ private Error logAndFail(String message, Throwable innerException) {
+ log(message);
+ log(" Exception=" + innerException);
+ throw new AssertionError(message, innerException);
+ }
+
+ public TestClass getTestClass() {
+ return mTestClsas;
+ }
+
+ /**
+ * Constructor.
+ */
+ public RavenwoodAwareTestRunner(Class<?> testClass) {
+ mTestClsas = new TestClass(testClass);
+
+ /*
+ * If the class has @DisabledOnRavenwood, then we'll delegate to ClassSkippingTestRunner,
+ * which simply skips it.
+ */
+ if (isOnRavenwood() && !shouldRunCassOnRavenwood(mTestClsas.getJavaClass())) {
+ mRealRunner = new ClassSkippingTestRunner(mTestClsas);
+ return;
+ }
+
+ // Find the real runner.
+ final Class<? extends Runner> realRunner;
+ final InnerRunner innerRunnerAnnotation = mTestClsas.getAnnotation(InnerRunner.class);
+ if (innerRunnerAnnotation != null) {
+ realRunner = innerRunnerAnnotation.value();
+ } else {
+ // Default runner.
+ realRunner = BlockJUnit4ClassRunner.class;
+ }
+
+ onRunnerInitializing();
+
+ try {
+ log("Initializing the inner runner: " + realRunner);
+
+ mRealRunner = realRunner.getConstructor(Class.class).newInstance(testClass);
+
+ } catch (InstantiationException | IllegalAccessException
+ | InvocationTargetException | NoSuchMethodException e) {
+ throw logAndFail("Failed to instantiate " + realRunner, e);
+ }
+ }
+
+ /**
+ * Run the bare minimum setup to initialize the wrapped runner.
+ */
+ // This method is called by the ctor, so never make it virtual.
+ private void onRunnerInitializing() {
+ if (!isOnRavenwood()) {
+ return;
+ }
+
+ log("onRunnerInitializing");
+
+ RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClsas);
+
+ // Hook point to allow more customization.
+ runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
+ }
+
+ private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass,
+ Object instance) {
+ if (!isOnRavenwood()) {
+ return;
+ }
+ log("runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
+
+ for (var method : getTestClass().getAnnotatedMethods(annotationClass)) {
+ ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
+
+ var methodDesc = method.getDeclaringClass().getName() + "."
+ + method.getMethod().toString();
+ try {
+ method.getMethod().invoke(instance);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw logAndFail("Caught exception while running method " + methodDesc, e);
+ }
+ }
+ }
+
+ @Override
+ public Description getDescription() {
+ return mRealRunner.getDescription();
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ if (mRealRunner instanceof ClassSkippingTestRunner) {
+ mRealRunner.run(notifier);
+ return;
+ }
+
+ sCurrentRunner.set(this);
+ try {
+ runWithHooks(getDescription(), Scope.Runner, Order.First,
+ () -> mRealRunner.run(notifier));
+ } finally {
+ sCurrentRunner.remove();
+ }
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (mRealRunner instanceof Filterable r) {
+ r.filter(filter);
+ }
+ }
+
+ @Override
+ public void order(Orderer orderer) throws InvalidOrderingException {
+ if (mRealRunner instanceof Orderable r) {
+ r.order(orderer);
+ }
+ }
+
+ @Override
+ public void sort(Sorter sorter) {
+ if (mRealRunner instanceof Sortable r) {
+ r.sort(sorter);
+ }
+ }
+
+ private Statement updateStatement(Statement base, Description description, Scope scope,
+ Order order) {
+ if (!isOnRavenwood()) {
+ return base;
+ }
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ runWithHooks(description, scope, order, base);
+ }
+ };
+ }
+
+ private void runWithHooks(Description description, Scope scope, Order order, Runnable r) {
+ runWithHooks(description, scope, order, new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ r.run();
+ }
+ });
+ }
+
+ private void runWithHooks(Description description, Scope scope, Order order, Statement s) {
+ Throwable th = null;
+ if (isOnRavenwood()) {
+ Assume.assumeTrue(
+ RavenwoodAwareTestRunnerHook.onBefore(this, description, scope, order));
+ }
+ try {
+ s.evaluate();
+ } catch (Throwable t) {
+ th = t;
+ SneakyThrow.sneakyThrow(t);
+ } finally {
+ if (isOnRavenwood()) {
+ RavenwoodAwareTestRunnerHook.onAfter(this, description, scope, order, th);
+ }
+ }
+ }
+
+ /**
+ * A runner that simply skips a class. It still has to support {@link Filterable}
+ * because otherwise the result still says "SKIPPED" even when it's not included in the
+ * filter.
+ */
+ private static class ClassSkippingTestRunner extends Runner implements Filterable {
+ private final TestClass mTestClass;
+ private final Description mDescription;
+ private boolean mFilteredOut;
+
+ ClassSkippingTestRunner(TestClass testClass) {
+ mTestClass = testClass;
+ mDescription = Description.createTestDescription(
+ testClass.getJavaClass(), testClass.getJavaClass().getSimpleName());
+ mFilteredOut = false;
+ }
+
+ @Override
+ public Description getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ if (mFilteredOut) {
+ return;
+ }
+ notifier.fireTestSuiteStarted(mDescription);
+ notifier.fireTestIgnored(mDescription);
+ notifier.fireTestSuiteFinished(mDescription);
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (filter.shouldRun(mDescription)) {
+ mFilteredOut = false;
+ } else {
+ throw new NoTestsRemainException();
+ }
+ }
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 74de444904ea..75faafb7fe58 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -60,10 +60,12 @@ public class RavenwoodRule implements TestRule {
/**
* When probing is enabled, all tests will be unconditionally run on Ravenwood to detect
- * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}.
+ * cases where a test is able to pass despite being marked as {@link DisabledOnRavenwood}.
*
* This is typically helpful for internal maintainers discovering tests that had previously
* been ignored, but now have enough Ravenwood-supported functionality to be enabled.
+ *
+ * TODO: Rename it to a more descriptive name.
*/
static final boolean ENABLE_PROBE_IGNORED = "1".equals(
System.getenv("RAVENWOOD_RUN_DISABLED_TESTS"));
@@ -281,7 +283,7 @@ public class RavenwoodRule implements TestRule {
* annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
* an {@link DisabledOnRavenwood} annotation.
*/
- static boolean shouldEnableOnRavenwood(Description description) {
+ public static boolean shouldEnableOnRavenwood(Description description) {
// First, consult any method-level annotations
if (description.isTest()) {
// Stopgap for http://g/ravenwood/EPAD-N5ntxM
@@ -300,20 +302,21 @@ public class RavenwoodRule implements TestRule {
}
// Otherwise, consult any class-level annotations
- final var clazz = description.getTestClass();
+ return shouldRunCassOnRavenwood(description.getTestClass());
+ }
+
+ public static boolean shouldRunCassOnRavenwood(Class<?> clazz) {
if (clazz != null) {
- if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
+ if (clazz.getAnnotation(EnabledOnRavenwood.class) != null) {
return true;
}
- if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
+ if (clazz.getAnnotation(DisabledOnRavenwood.class) != null) {
return false;
}
- if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ if (clazz.getAnnotation(IgnoreUnderRavenwood.class) != null) {
return false;
}
}
-
- // When no annotations have been requested, assume test should be included
return true;
}
@@ -364,6 +367,7 @@ public class RavenwoodRule implements TestRule {
commonPrologue(base, description);
try {
base.evaluate();
+
RavenwoodRuleImpl.logTestRunner("finished", description);
} catch (Throwable t) {
RavenwoodRuleImpl.logTestRunner("failed", description);
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
new file mode 100644
index 000000000000..6b80e0cbf91e
--- /dev/null
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.ravenwood;
+
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Provide hook points created by {@link RavenwoodAwareTestRunner}.
+ */
+public class RavenwoodAwareTestRunnerHook {
+ private RavenwoodAwareTestRunnerHook() {
+ }
+
+ /**
+ * Called when a runner starts, befre the inner runner gets a chance to run.
+ */
+ public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+ // No-op on a real device.
+ }
+
+ public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
+ Scope scope, Order order) {
+ // No-op on a real device.
+ return true;
+ }
+
+ public static void onAfter(RavenwoodAwareTestRunner runner, Description description,
+ Scope scope, Order order, Throwable th) {
+ // No-op on a real device.
+ }
+}
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 129802378cd4..7b5bc5aeb7b6 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -21,6 +21,8 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.Arrays;
public class RavenwoodCommonUtils {
@@ -42,12 +44,17 @@ public class RavenwoodCommonUtils {
private static final boolean IS_ON_RAVENWOOD = RavenwoodDivergence.isOnRavenwood();
- private static final String RAVEWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
+ private static final String RAVENWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
public static final String RAVENWOOD_SYSPROP = "ro.is_on_ravenwood";
public static final String RAVENWOOD_RESOURCE_APK = "ravenwood-res-apks/ravenwood-res.apk";
+ public static final String RAVENWOOD_EMPTY_RESOURCES_APK =
+ RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk";
+
+ public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
+
// @GuardedBy("sLock")
private static boolean sIntegrityChecked = false;
@@ -74,6 +81,18 @@ public class RavenwoodCommonUtils {
return sEnableExtraRuntimeCheck;
}
+ /** Simple logging method. */
+ public static void log(String tag, String message) {
+ // Avoid using Android's Log class, which could be broken for various reasons.
+ // (e.g. the JNI file doesn't exist for whatever reason)
+ System.out.print(tag + ": " + message + "\n");
+ }
+
+ /** Simple logging method. */
+ private void log(String tag, String format, Object... args) {
+ log(tag, String.format(format, args));
+ }
+
/**
* Load the main runtime JNI library.
*/
@@ -178,7 +197,7 @@ public class RavenwoodCommonUtils {
*/
public static String getRavenwoodRuntimePath() {
ensureOnRavenwood();
- return RAVEWOOD_RUNTIME_PATH;
+ return RAVENWOOD_RUNTIME_PATH;
}
private static String getRavenwoodRuntimePathInternal() {
@@ -233,4 +252,17 @@ public class RavenwoodCommonUtils {
var is = new FileInputStream(fd);
RavenwoodCommonUtils.closeQuietly(is);
}
+
+ public static void ensureIsPublicVoidMethod(Method method, boolean isStatic) {
+ var ok = Modifier.isPublic(method.getModifiers())
+ && (Modifier.isStatic(method.getModifiers()) == isStatic)
+ && (method.getReturnType() == void.class);
+ if (ok) {
+ return; // okay
+ }
+ throw new AssertionError(String.format(
+ "Method %s.%s() expected to be public %svoid",
+ method.getDeclaringClass().getName(), method.getName(),
+ (isStatic ? "static " : "")));
+ }
}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
index 706a055c9faf..f894b0e69a90 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
@@ -37,6 +37,9 @@ public class RavenwoodEnvironment_host {
* Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
*/
public static void ensureRavenwoodInitialized() {
+
+ // TODO Unify it with the initialization code in RavenwoodAwareTestRunnerHook.
+
synchronized (sInitializeLock) {
if (sInitialized) {
return;
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index 7371d0a029b9..a5c0b54a8637 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -15,8 +15,8 @@
*/
package android.system;
+import com.android.ravenwood.RavenwoodRuntimeNative;
import com.android.ravenwood.common.JvmWorkaround;
-import com.android.ravenwood.common.RavenwoodRuntimeNative;
import java.io.FileDescriptor;
import java.io.FileInputStream;
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java
new file mode 100644
index 000000000000..96aed4b3401d
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwood;
+
+import com.android.ravenwood.common.JvmWorkaround;
+
+import java.io.FileDescriptor;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Class to host APIs that exist in libcore, but not in standard JRE.
+ */
+public class RavenwoodJdkPatch {
+ /**
+ * Implements FileDescriptor.getInt$()
+ */
+ public static int getInt$(FileDescriptor fd) {
+ return JvmWorkaround.getInstance().getFdInt(fd);
+ }
+
+ /**
+ * Implements FileDescriptor.setInt$(int)
+ */
+ public static void setInt$(FileDescriptor fd, int rawFd) {
+ JvmWorkaround.getInstance().setFdInt(fd, rawFd);
+ }
+
+ /**
+ * Implements LinkedHashMap.eldest()
+ */
+ public static <K, V> Map.Entry<K, V> eldest(LinkedHashMap<K, V> map) {
+ final var it = map.entrySet().iterator();
+ return it.hasNext() ? it.next() : null;
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index beba83391652..0d8408c12033 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.common;
+package com.android.ravenwood;
import android.system.ErrnoException;
import android.system.StructStat;
+import com.android.ravenwood.common.JvmWorkaround;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
import java.io.FileDescriptor;
/**
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index ed5a587cdccc..ba89f71dde8a 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -34,11 +34,11 @@ public class VMRuntime {
}
public boolean is64Bit() {
- return true;
+ return "amd64".equals(System.getProperty("os.arch"));
}
public static boolean is64BitAbi(String abi) {
- return true;
+ return abi.contains("64");
}
public Object newUnpaddedArray(Class<?> componentType, int minLength) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java
index 65c285e06bf8..2bd1ae89c824 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java
@@ -16,7 +16,13 @@
package libcore.io;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.ravenwood.common.JvmWorkaround;
+
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.net.Socket;
@@ -47,6 +53,13 @@ public final class IoUtils {
}
}
+ public static void closeQuietly(FileDescriptor fd) {
+ try {
+ Os.close(fd);
+ } catch (ErrnoException ignored) {
+ }
+ }
+
public static void deleteContents(File dir) throws IOException {
File[] files = dir.listFiles();
if (files != null) {
@@ -58,4 +71,17 @@ public final class IoUtils {
}
}
}
+
+ /**
+ * FD owners currently unsupported under Ravenwood; ignored
+ */
+ public static void setFdOwner(FileDescriptor fd, Object owner) {
+ }
+
+ /**
+ * FD owners currently unsupported under Ravenwood; return FD directly
+ */
+ public static int acquireRawFd(FileDescriptor fd) {
+ return JvmWorkaround.getInstance().getFdInt(fd);
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
index 14b5a4f0c1e0..4e7dc5d6264f 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -15,7 +15,7 @@
*/
package libcore.util;
-import com.android.ravenwood.common.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeNative;
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index c8049281bc53..f5cb019f4e7e 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -245,7 +245,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
g_StructStat = findClass(env, "android/system/StructStat");
g_StructTimespecClass = findClass(env, "android/system/StructTimespec");
- jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/common/RavenwoodRuntimeNative",
+ jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/RavenwoodRuntimeNative",
sMethods, NELEM(sMethods));
if (res < 0) {
return res;
diff --git a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index 044239f06297..b3d3963270ee 100644
--- a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -16,6 +16,7 @@
package com.android.ravenwoodtest.servicestest;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -62,7 +63,9 @@ public class RavenwoodServicesTest {
final SerialManager service = (SerialManager)
mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
final String[] ports = service.getSerialPorts();
- assertEquals(0, ports.length);
+ final String[] refPorts = mRavenwood.getContext().getResources().getStringArray(
+ com.android.internal.R.array.config_serialPorts);
+ assertArrayEquals(refPorts, ports);
}
@Test
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 5cffdeccbacf..d8366c58c50d 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -99,6 +99,7 @@ android.util.SparseDoubleArray
android.util.SparseIntArray
android.util.SparseLongArray
android.util.SparseSetArray
+android.util.StateSet
android.util.StringBuilderPrinter
android.util.TeeWriter
android.util.TimeUtils
@@ -222,9 +223,11 @@ android.content.res.ApkAssets
android.content.res.AssetFileDescriptor
android.content.res.AssetManager
android.content.res.AssetManager$Builder
+android.content.res.ColorStateList
android.content.res.ConfigurationBoundResourceCache
android.content.res.Configuration
android.content.res.CompatibilityInfo
+android.content.res.ComplexColor
android.content.res.ConstantState
android.content.res.DrawableCache
android.content.res.Element
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 2d49128ae292..d962c8232bf7 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -17,6 +17,13 @@ class :r keepclass
rename com/.*/nano/ devicenano/
rename android/.*/nano/ devicenano/
+# Support APIs not available in standard JRE
+class java.io.FileDescriptor keep
+ method getInt$ ()I @com.android.ravenwood.RavenwoodJdkPatch.getInt$
+ method setInt$ (I)V @com.android.ravenwood.RavenwoodJdkPatch.setInt$
+class java.util.LinkedHashMap keep
+ method eldest ()Ljava/util/Map$Entry; @com.android.ravenwood.RavenwoodJdkPatch.eldest
+
# Exported to Mainline modules; cannot use annotations
class com.android.internal.util.FastXmlSerializer keepclass
class com.android.internal.util.FileRotator keepclass
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
new file mode 100644
index 000000000000..3a7fab39e4ac
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("ktlint:standard:filename")
+
+package com.android.platform.test.ravenwood.ravenizer
+
+/**
+ * Use it for internal exception that really shouldn't happen.
+ */
+class RavenizerInternalException(message: String) : Exception(message)
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index da9c7d97dac5..e92ef7216e25 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -20,7 +20,7 @@ import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.zipEntryNameToClassName
import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
-import com.android.platform.test.ravenwood.ravenizer.adapter.TestRunnerRewritingAdapter
+import com.android.platform.test.ravenwood.ravenizer.adapter.RunnerRewritingAdapter
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
@@ -177,7 +177,8 @@ class Ravenizer(val options: RavenizerOptions) {
* Whether a class needs to be processed. This must be kept in sync with [processSingleClass].
*/
private fun shouldProcessClass(classes: ClassNodes, classInternalName: String): Boolean {
- return TestRunnerRewritingAdapter.shouldProcess(classes, classInternalName)
+ return !classInternalName.shouldByBypassed()
+ && RunnerRewritingAdapter.shouldProcess(classes, classInternalName)
}
private fun processSingleClass(
@@ -191,6 +192,9 @@ class Ravenizer(val options: RavenizerOptions) {
lateinit var data: ByteArray
stats.totalConversionTime += log.vTime("Modify ${entry.name}") {
+
+ val classInternalName = zipEntryNameToClassName(entry.name)
+ ?: throw RavenizerInternalException("Unexpected zip entry name: ${entry.name}")
val flags = ClassWriter.COMPUTE_MAXS
val cw = ClassWriter(flags)
var outVisitor: ClassVisitor = cw
@@ -201,7 +205,8 @@ class Ravenizer(val options: RavenizerOptions) {
}
// This must be kept in sync with shouldProcessClass.
- outVisitor = TestRunnerRewritingAdapter(allClasses, outVisitor)
+ outVisitor = RunnerRewritingAdapter.maybeApply(
+ classInternalName, allClasses, outVisitor)
cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 0018648998dc..e026e7ab3679 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -15,18 +15,31 @@
*/
package com.android.platform.test.ravenwood.ravenizer
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.startsWithAny
+import org.junit.rules.TestRule
+import org.junit.runner.RunWith
import org.objectweb.asm.Type
-val junitTestMethodType = Type.getType(org.junit.Test::class.java)
-val junitRunWithType = Type.getType(org.junit.runner.RunWith::class.java)
+data class TypeHolder(
+ val clazz: Class<*>,
+) {
+ val type = Type.getType(clazz)
+ val desc = type.descriptor
+ val descAsSet = setOf<String>(desc)
+ val internlName = type.internalName
+}
-val junitTestMethodDescriptor = junitTestMethodType.descriptor
-val junitRunWithDescriptor = junitRunWithType.descriptor
+val testAnotType = TypeHolder(org.junit.Test::class.java)
+val ruleAnotType = TypeHolder(org.junit.Rule::class.java)
+val classRuleAnotType = TypeHolder(org.junit.ClassRule::class.java)
+val runWithAnotType = TypeHolder(RunWith::class.java)
+val innerRunnerAnotType = TypeHolder(RavenwoodAwareTestRunner.InnerRunner::class.java)
-val junitTestMethodDescriptors = setOf<String>(junitTestMethodDescriptor)
-val junitRunWithDescriptors = setOf<String>(junitRunWithDescriptor)
+val testRuleType = TypeHolder(TestRule::class.java)
+val ravenwoodTestRunnerType = TypeHolder(RavenwoodAwareTestRunner::class.java)
/**
* Returns true, if a test looks like it's a test class which needs to be processed.
@@ -39,16 +52,44 @@ fun isTestLookingClass(classes: ClassNodes, className: String): Boolean {
val cn = classes.findClass(className) ?: return false
- if (cn.findAnyAnnotation(junitRunWithDescriptors) != null) {
+ if (cn.findAnyAnnotation(runWithAnotType.descAsSet) != null) {
return true
}
cn.methods?.forEach { method ->
- if (method.findAnyAnnotation(junitTestMethodDescriptors) != null) {
+ if (method.findAnyAnnotation(testAnotType.descAsSet) != null) {
return true
}
}
+
+ // Check the super class.
if (cn.superName == null) {
return false
}
return isTestLookingClass(classes, cn.superName)
}
+
+fun String.isRavenwoodClass(): Boolean {
+ return this.startsWithAny(
+ "com/android/hoststubgen/",
+ "android/platform/test/ravenwood",
+ "com/android/ravenwood/",
+ "com/android/platform/test/ravenwood/",
+ )
+}
+
+/**
+ * Classes that should never be modified.
+ */
+fun String.shouldByBypassed(): Boolean {
+ if (this.isRavenwoodClass()) {
+ return true
+ }
+ return this.startsWithAny(
+ "java/", // just in case...
+ "javax/",
+ "org/junit/",
+ "org/mockito/",
+ "kotlin/",
+ // TODO -- anything else?
+ )
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
new file mode 100644
index 000000000000..25cad0213b72
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.platform.test.ravenwood.ravenizer.adapter
+
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner
+import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.findAnnotationValueAsType
+import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.log
+import com.android.hoststubgen.visitors.OPCODE_VERSION
+import com.android.platform.test.ravenwood.ravenizer.RavenizerInternalException
+import com.android.platform.test.ravenwood.ravenizer.classRuleAnotType
+import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
+import com.android.platform.test.ravenwood.ravenizer.innerRunnerAnotType
+import com.android.platform.test.ravenwood.ravenizer.ravenwoodTestRunnerType
+import com.android.platform.test.ravenwood.ravenizer.ruleAnotType
+import com.android.platform.test.ravenwood.ravenizer.runWithAnotType
+import com.android.platform.test.ravenwood.ravenizer.testRuleType
+import org.objectweb.asm.AnnotationVisitor
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.FieldVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Opcodes.ACC_FINAL
+import org.objectweb.asm.Opcodes.ACC_PUBLIC
+import org.objectweb.asm.Opcodes.ACC_STATIC
+import org.objectweb.asm.commons.AdviceAdapter
+import org.objectweb.asm.tree.ClassNode
+
+/**
+ * Class visitor to update the RunWith and inject some necessary rules.
+ *
+ * - Change the @RunWith(RavenwoodAwareTestRunner.class).
+ * - If the original class has a @RunWith(...), then change it to an @OrigRunWith(...).
+ * - Add RavenwoodAwareTestRunner's member rules as junit rules.
+ * - Update the order of the existing JUnit rules to make sure they don't use the MIN or MAX.
+ */
+class RunnerRewritingAdapter private constructor(
+ protected val classes: ClassNodes,
+ nextVisitor: ClassVisitor,
+) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
+ /** Arbitrary cut-off point when deciding whether to change the order or an existing rule.*/
+ val RULE_ORDER_TWEAK_CUTOFF = 1973020500
+
+ /** Current class's internal name */
+ lateinit var classInternalName: String
+
+ /** [ClassNode] for the current class */
+ lateinit var classNode: ClassNode
+
+ /** True if this visitor is generating code. */
+ var isGeneratingCode = false
+
+ /** Run a [block] with [isGeneratingCode] set to true. */
+ private inline fun <T> generateCode(block: () -> T): T {
+ isGeneratingCode = true
+ try {
+ return block()
+ } finally {
+ isGeneratingCode = false
+ }
+ }
+
+ override fun visit(
+ version: Int,
+ access: Int,
+ name: String?,
+ signature: String?,
+ superName: String?,
+ interfaces: Array<out String>?,
+ ) {
+ classInternalName = name!!
+ classNode = classes.getClass(name)
+ if (!isTestLookingClass(classes, name)) {
+ throw RavenizerInternalException("This adapter shouldn't be used for non-test class")
+ }
+ super.visit(version, access, name, signature, superName, interfaces)
+
+ generateCode {
+ injectRunWithAnnotation()
+ if (!classes.hasClassInitializer(classInternalName)) {
+ injectStaticInitializer()
+ }
+ injectRules()
+ }
+ }
+
+ /**
+ * Remove the original @RunWith annotation.
+ */
+ override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
+ if (!isGeneratingCode && runWithAnotType.desc == descriptor) {
+ return null
+ }
+ return super.visitAnnotation(descriptor, visible)
+ }
+
+ override fun visitField(
+ access: Int,
+ name: String,
+ descriptor: String,
+ signature: String?,
+ value: Any?
+ ): FieldVisitor {
+ val fallback = super.visitField(access, name, descriptor, signature, value)
+ if (isGeneratingCode) {
+ return fallback
+ }
+ return FieldRuleOrderRewriter(name, fallback)
+ }
+
+ /** Inject an empty <clinit>. The body will be injected by [visitMethod]. */
+ private fun injectStaticInitializer() {
+ visitMethod(
+ Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC,
+ CLASS_INITIALIZER_NAME,
+ CLASS_INITIALIZER_DESC,
+ null,
+ null
+ )!!.let { mv ->
+ mv.visitCode()
+ mv.visitInsn(Opcodes.RETURN)
+ mv.visitMaxs(0, 0)
+ mv.visitEnd()
+ }
+ }
+
+ /**
+ * Inject `@RunWith(RavenwoodAwareTestRunner.class)`. If the class already has
+ * a `@RunWith`, then change it to add a `@OrigRunWith`.
+ */
+ private fun injectRunWithAnnotation() {
+ // Extract the original RunWith annotation and its value.
+ val runWith = classNode.findAnyAnnotation(runWithAnotType.descAsSet)
+ val runWithClass = runWith?.let { an ->
+ findAnnotationValueAsType(an, "value")
+ }
+
+ if (runWith != null) {
+ if (runWithClass == ravenwoodTestRunnerType.type) {
+ // It already uses RavenwoodTestRunner. We'll just keep it, but we need to
+ // inject it again because the original one is removed by visitAnnotation().
+ log.d("Class ${classInternalName.toHumanReadableClassName()}" +
+ " already uses RavenwoodTestRunner.")
+ visitAnnotation(runWithAnotType.desc, true)!!.let { av ->
+ av.visit("value", ravenwoodTestRunnerType)
+ av.visitEnd()
+ }
+ return
+ }
+ if (runWithClass == null) {
+ throw ClassParseException("@RunWith annotation doesn't have a property \"value\""
+ + " in class ${classInternalName.toHumanReadableClassName()}")
+ }
+
+ // Inject an @OrigRunWith.
+ visitAnnotation(innerRunnerAnotType.desc, true)!!.let { av ->
+ av.visit("value", runWithClass)
+ av.visitEnd()
+ }
+ }
+
+ // Inject a @RunWith(RavenwoodAwareTestRunner.class).
+ visitAnnotation(runWithAnotType.desc, true)!!.let { av ->
+ av.visit("value", ravenwoodTestRunnerType.type)
+ av.visitEnd()
+ }
+ log.d("Processed ${classInternalName.toHumanReadableClassName()}")
+ }
+
+ /*
+ Generate the fields and the ctor, which should looks like this:
+
+ public static final org.junit.rules.TestRule sRavenwoodImplicitClassMinRule;
+ descriptor: Lorg/junit/rules/TestRule;
+ flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ 0: #49(#50=I#51)
+ org.junit.ClassRule(
+ order=-2147483648
+ )
+
+ public static final org.junit.rules.TestRule sRavenwoodImplicitClassMaxRule;
+ descriptor: Lorg/junit/rules/TestRule;
+ flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ 0: #49(#50=I#52)
+ org.junit.ClassRule(
+ order=2147483647
+ )
+
+ public final org.junit.rules.TestRule sRavenwoodImplicitInstanceMinRule;
+ descriptor: Lorg/junit/rules/TestRule;
+ flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ 0: #53(#50=I#51)
+ org.junit.Rule(
+ order=-2147483648
+ )
+
+ public final org.junit.rules.TestRule sRavenwoodImplicitInstanceMaxRule;
+ descriptor: Lorg/junit/rules/TestRule;
+ flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ 0: #53(#50=I#52)
+ org.junit.Rule(
+ order=2147483647
+ )
+ */
+
+ val sRavenwood_ClassRuleMin = "sRavenwood_ClassRuleMin"
+ val sRavenwood_ClassRuleMax = "sRavenwood_ClassRuleMax"
+ val mRavenwood_InstRuleMin = "mRavenwood_InstRuleMin"
+ val mRavenwood_InstRuleMax = "mRavenwood_InstRuleMax"
+
+ private fun injectRules() {
+ injectRule(sRavenwood_ClassRuleMin, true, Integer.MIN_VALUE)
+ injectRule(sRavenwood_ClassRuleMax, true, Integer.MAX_VALUE)
+ injectRule(mRavenwood_InstRuleMin, false, Integer.MIN_VALUE)
+ injectRule(mRavenwood_InstRuleMax, false, Integer.MAX_VALUE)
+ }
+
+ private fun injectRule(fieldName: String, isStatic: Boolean, order: Int) {
+ visitField(
+ ACC_PUBLIC or ACC_FINAL or (if (isStatic) ACC_STATIC else 0),
+ fieldName,
+ testRuleType.desc,
+ null,
+ null,
+ ).let { fv ->
+ val anot = if (isStatic) { classRuleAnotType } else { ruleAnotType }
+ fv.visitAnnotation(anot.desc, true).let {
+ it.visit("order", order)
+ it.visitEnd()
+ }
+ fv.visitEnd()
+ }
+ }
+
+ override fun visitMethod(
+ access: Int,
+ name: String,
+ descriptor: String,
+ signature: String?,
+ exceptions: Array<String>?,
+ ): MethodVisitor {
+ val next = super.visitMethod(access, name, descriptor, signature, exceptions)
+ if (name == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) {
+ return ClassInitializerVisitor(
+ access, name, descriptor, signature, exceptions, next)
+ }
+ if (name == CTOR_NAME) {
+ return ConstructorVisitor(
+ access, name, descriptor, signature, exceptions, next)
+ }
+ return next
+ }
+
+ /*
+
+ static {};
+ descriptor: ()V
+ flags: (0x0008) ACC_STATIC
+ Code:
+ stack=1, locals=0, args_size=0
+ 0: getstatic #36 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitClassMinRule:Lorg/junit/rules/TestRule;
+ 3: putstatic #39 // Field sRavenwoodImplicitClassMinRule:Lorg/junit/rules/TestRule;
+ 6: getstatic #42 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitClassMaxRule:Lorg/junit/rules/TestRule;
+ 9: putstatic #45 // Field sRavenwoodImplicitClassMaxRule:Lorg/junit/rules/TestRule;
+ 12: return
+ LineNumberTable:
+ line 33: 0
+ line 36: 6
+ */
+ private inner class ClassInitializerVisitor(
+ access: Int,
+ val name: String,
+ val descriptor: String,
+ signature: String?,
+ exceptions: Array<String>?,
+ next: MethodVisitor?,
+ ) : MethodVisitor(OPCODE_VERSION, next) {
+ override fun visitCode() {
+ visitFieldInsn(Opcodes.GETSTATIC,
+ ravenwoodTestRunnerType.internlName,
+ RavenwoodAwareTestRunner.IMPLICIT_CLASS_MIN_RULE_NAME,
+ testRuleType.desc
+ )
+ visitFieldInsn(Opcodes.PUTSTATIC,
+ classInternalName,
+ sRavenwood_ClassRuleMin,
+ testRuleType.desc
+ )
+
+ visitFieldInsn(Opcodes.GETSTATIC,
+ ravenwoodTestRunnerType.internlName,
+ RavenwoodAwareTestRunner.IMPLICIT_CLASS_MAX_RULE_NAME,
+ testRuleType.desc
+ )
+ visitFieldInsn(Opcodes.PUTSTATIC,
+ classInternalName,
+ sRavenwood_ClassRuleMax,
+ testRuleType.desc
+ )
+
+ super.visitCode()
+ }
+ }
+
+ /*
+ public com.android.ravenwoodtest.bivalenttest.runnertest.RavenwoodRunnerTest();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=1, args_size=1
+ 0: aload_0
+ 1: invokespecial #1 // Method java/lang/Object."<init>":()V
+ 4: aload_0
+ 5: getstatic #7 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitInstanceMinRule:Lorg/junit/rules/TestRule;
+ 8: putfield #13 // Field sRavenwoodImplicitInstanceMinRule:Lorg/junit/rules/TestRule;
+ 11: aload_0
+ 12: getstatic #18 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitInstanceMaxRule:Lorg/junit/rules/TestRule;
+ 15: putfield #21 // Field sRavenwoodImplicitInstanceMaxRule:Lorg/junit/rules/TestRule;
+ 18: return
+ LineNumberTable:
+ line 31: 0
+ line 38: 4
+ line 41: 11
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 19 0 this Lcom/android/ravenwoodtest/bivalenttest/runnertest/RavenwoodRunnerTest;
+ */
+ private inner class ConstructorVisitor(
+ access: Int,
+ name: String,
+ descriptor: String,
+ signature: String?,
+ exceptions: Array<String>?,
+ next: MethodVisitor?,
+ ) : AdviceAdapter(OPCODE_VERSION, next, ACC_ENUM, name, descriptor) {
+ override fun onMethodEnter() {
+ visitVarInsn(ALOAD, 0)
+ visitFieldInsn(Opcodes.GETSTATIC,
+ ravenwoodTestRunnerType.internlName,
+ RavenwoodAwareTestRunner.IMPLICIT_INST_MIN_RULE_NAME,
+ testRuleType.desc
+ )
+ visitFieldInsn(Opcodes.PUTFIELD,
+ classInternalName,
+ mRavenwood_InstRuleMin,
+ testRuleType.desc
+ )
+
+ visitVarInsn(ALOAD, 0)
+ visitFieldInsn(Opcodes.GETSTATIC,
+ ravenwoodTestRunnerType.internlName,
+ RavenwoodAwareTestRunner.IMPLICIT_INST_MAX_RULE_NAME,
+ testRuleType.desc
+ )
+ visitFieldInsn(Opcodes.PUTFIELD,
+ classInternalName,
+ mRavenwood_InstRuleMax,
+ testRuleType.desc
+ )
+ }
+ }
+
+ /**
+ * Rewrite "order" of the existing junit rules to make sure no rules use a MAX or MIN order.
+ *
+ * Currently, we do it a hacky way -- use an arbitrary cut-off point, and if the order
+ * is larger than that, decrement by 1, and if it's smaller than the negative cut-off point,
+ * increment it by 1.
+ *
+ * (or the arbitrary number is already used.... then we're unlucky, let's change the cut-off
+ * point.)
+ */
+ private inner class FieldRuleOrderRewriter(
+ val fieldName: String,
+ next: FieldVisitor,
+ ) : FieldVisitor(OPCODE_VERSION, next) {
+ override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
+ val fallback = super.visitAnnotation(descriptor, visible)
+ if (descriptor != ruleAnotType.desc && descriptor != classRuleAnotType.desc) {
+ return fallback
+ }
+ return RuleOrderRewriter(fallback)
+ }
+
+ private inner class RuleOrderRewriter(
+ next: AnnotationVisitor,
+ ) : AnnotationVisitor(OPCODE_VERSION, next) {
+ override fun visit(name: String?, origValue: Any?) {
+ if (name != "order") {
+ return super.visit(name, origValue)
+ }
+ var order = origValue as Int
+ if (order == RULE_ORDER_TWEAK_CUTOFF || order == -RULE_ORDER_TWEAK_CUTOFF) {
+ // Oops. If this happens, we'll need to change RULE_ORDER_TWEAK_CUTOFF.
+ // Or, we could scan all the rules in the target jar and find an unused number.
+ // Because rules propagate to subclasses, we'll at least check all the
+ // super classes of the current class.
+ throw RavenizerInternalException(
+ "OOPS: Field $classInternalName.$fieldName uses $order."
+ + " We can't update it.")
+ }
+ if (order > RULE_ORDER_TWEAK_CUTOFF) {
+ order -= 1
+ }
+ if (order < -RULE_ORDER_TWEAK_CUTOFF) {
+ order += 1
+ }
+ super.visit(name, order)
+ }
+ }
+ }
+
+ companion object {
+ fun shouldProcess(classes: ClassNodes, className: String): Boolean {
+ return isTestLookingClass(classes, className)
+ }
+
+ fun maybeApply(
+ className: String,
+ classes: ClassNodes,
+ nextVisitor: ClassVisitor,
+ ): ClassVisitor {
+ if (!shouldProcess(classes, className)) {
+ return nextVisitor
+ } else {
+ return RunnerRewritingAdapter(classes, nextVisitor)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
deleted file mode 100644
index c5399084fb33..000000000000
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.platform.test.ravenwood.ravenizer.adapter
-
-import com.android.hoststubgen.asm.ClassNodes
-import com.android.hoststubgen.visitors.OPCODE_VERSION
-import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
-import org.objectweb.asm.ClassVisitor
-
-/**
- * Class visitor to rewrite the test runner for Ravenwood
- *
- * TODO: Implement it.
- */
-class TestRunnerRewritingAdapter(
- protected val classes: ClassNodes,
- nextVisitor: ClassVisitor,
-) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
- companion object {
- /**
- * Returns true if a target class is interesting to this adapter.
- */
- fun shouldProcess(classes: ClassNodes, className: String): Boolean {
- return isTestLookingClass(classes, className)
- }
- }
-}
diff --git a/services/Android.bp b/services/Android.bp
index 0006455f41b0..653cd3c3b680 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -136,6 +136,7 @@ filegroup {
":services.searchui-sources",
":services.smartspace-sources",
":services.soundtrigger-sources",
+ ":services.supervision-sources",
":services.systemcaptions-sources",
":services.translation-sources",
":services.texttospeech-sources",
@@ -237,6 +238,7 @@ system_java_library {
"services.searchui",
"services.smartspace",
"services.soundtrigger",
+ "services.supervision",
"services.systemcaptions",
"services.translation",
"services.texttospeech",
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index edb6390ea874..3224b27d5803 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -34,6 +34,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCE
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
import static com.android.window.flags.Flags.deleteCaptureDisplay;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -1100,11 +1101,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
if (svcConnTracingEnabled()) {
logTraceSvcConn("performGlobalAction", "action=" + action);
}
+ int currentUserId;
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return false;
}
+ currentUserId = mSystemSupport.getCurrentUserIdLocked();
}
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
final long identity = Binder.clearCallingIdentity();
try {
return mSystemActionPerformer.performSystemAction(action);
@@ -2750,6 +2754,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@RequiresNoPermission
@Override
public void setAnimationScale(float scale) {
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mSystemSupport.getCurrentUserIdLocked();
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
final long identity = Binder.clearCallingIdentity();
try {
Settings.Global.putFloat(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0aa750e84a22..7cbb97e56b01 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -63,6 +63,7 @@ import static com.android.internal.accessibility.util.AccessibilityUtils.isUserS
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -309,6 +310,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final PowerManager mPowerManager;
+ private final UserManager mUserManager;
+
private final WindowManagerInternal mWindowManagerService;
private final AccessibilitySecurityPolicy mSecurityPolicy;
@@ -507,6 +510,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
super(permissionEnforcer);
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mUserManager = mContext.getSystemService(UserManager.class);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
mTraceManager = AccessibilityTraceManager.getInstance(
mWindowManagerService.getAccessibilityController(), this, mLock);
@@ -542,6 +546,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
super(PermissionEnforcer.fromContext(context));
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
+ mUserManager = context.getSystemService(UserManager.class);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
mTraceManager = AccessibilityTraceManager.getInstance(
mWindowManagerService.getAccessibilityController(), this, mLock);
@@ -1263,6 +1268,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@EnforcePermission(MANAGE_ACCESSIBILITY)
public void registerSystemAction(RemoteAction action, int actionId) {
registerSystemAction_enforcePermission();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerSystemAction",
FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId);
@@ -1279,6 +1289,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@EnforcePermission(MANAGE_ACCESSIBILITY)
public void unregisterSystemAction(int actionId) {
unregisterSystemAction_enforcePermission();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction",
FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId);
@@ -1606,6 +1621,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@EnforcePermission(STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
notifyAccessibilityButtonClicked_enforcePermission();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
FLAGS_ACCESSIBILITY_MANAGER,
@@ -1634,6 +1654,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@EnforcePermission(STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
notifyAccessibilityButtonVisibilityChanged_enforcePermission();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged",
FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown);
@@ -1974,9 +1999,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
this, 0, oldUserState.mUserId));
}
- // Announce user changes only if more that one exist.
- UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- final boolean announceNewUser = userManager.getUsers().size() > 1;
+ // Announce user changes only if more than one exist.
+ final boolean announceNewUser = mUserManager.getUsers().size() > 1;
// The user changed.
mCurrentUserId = userId;
@@ -2017,10 +2041,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
synchronized (mLock) {
AccessibilityUserState userState = getCurrentUserStateLocked();
if (userState.isHandlingAccessibilityEventsLocked()) {
- UserManager userManager = (UserManager) mContext.getSystemService(
- Context.USER_SERVICE);
String message = mContext.getString(R.string.user_switched,
- userManager.getUserInfo(mCurrentUserId).name);
+ mUserManager.getUserInfo(mCurrentUserId).name);
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.getText().add(message);
@@ -3185,6 +3207,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ @GuardedBy("mLock")
private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState userState) {
// We observe windows for accessibility only if there is at least
// one bound service that can retrieve window content that specified
@@ -3211,6 +3234,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
if (display != null) {
+ // When supporting visible background users, only track windows on the display
+ // assigned to the current user. The proxy displays are registered only to the
+ // current user.
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProxyManager.isProxyedDisplay(display.getDisplayId())
+ && !mUmi.isUserVisible(mCurrentUserId, display.getDisplayId())) {
+ continue;
+ }
if (observingWindows) {
mA11yWindowManager.startTrackingWindows(display.getDisplayId(),
mProxyManager.isProxyedDisplay(display.getDisplayId()));
@@ -4799,6 +4830,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
throws RemoteException {
registerProxyForDisplay_enforcePermission();
mSecurityPolicy.checkForAccessibilityPermissionOrRole();
+ int currentUserId;
+ synchronized (mLock) {
+ currentUserId = mCurrentUserId;
+ }
+ enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
if (client == null) {
return false;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 6007bfd99e7b..9a81aa6cc506 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -594,10 +594,6 @@ public class AccessibilityWindowManager {
private boolean windowMattersToAccessibilityLocked(AccessibilityWindow a11yWindow,
int windowId, Region regionInScreen, Region unaccountedSpace) {
- if (a11yWindow.ignoreRecentsAnimationForAccessibility()) {
- return false;
- }
-
if (a11yWindow.isFocused()) {
return true;
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 89d796177da2..1b5b7e875db8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -233,6 +233,7 @@ java_library_static {
"stats_flags_lib",
"core_os_flags_lib",
"connectivity_flags_lib",
+ "device_config_service_flags_java",
"dreams_flags_lib",
"aconfig_new_storage_flags_lib",
"powerstats_flags_lib",
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index 82c2038d8011..dbf144f0c63e 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -56,16 +56,11 @@ public class SerialService extends ISerialManager.Stub {
}
}
- @android.ravenwood.annotation.RavenwoodReplace
private static String[] getSerialPorts(Context context) {
return context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
}
- private static String[] getSerialPorts$ravenwood(Context context) {
- return new String[0];
- }
-
public static class Lifecycle extends SystemService {
private SerialService mService;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f1bdc05cce3c..d80b38e32b6c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12158,7 +12158,7 @@ public class ActivityManagerService extends IActivityManager.Stub
opts.dumpProto = true;
} else if ("--logstats".equals(opt)) {
opts.mDumpAllocatorStats = true;
- } else if ("-h".equals(opt)) {
+ } else if ("-h".equals(opt) || "--help".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
pw.println(" -d: include dalvik details.");
@@ -12168,10 +12168,13 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println(" -p: dump also private dirty memory usage.");
pw.println(" --oom: only show processes organized by oom adj.");
pw.println(" --local: only collect details locally, don't call process.");
+ pw.println(" --logstats: dump native allocator stats to log");
pw.println(" --package: interpret process arg as package, dumping all");
pw.println(" processes that have loaded that package.");
pw.println(" --checkin: dump data for a checkin");
pw.println(" --proto: dump data to proto");
+ pw.println(" --logstats: log native allocator statistics.");
+ pw.println(" --unreachable: dump unreachable native memory with libmemunreachable.");
pw.println("If [process] is specified it can be the name or ");
pw.println("pid of a specific process to dump.");
return;
diff --git a/services/core/java/com/android/server/am/Android.bp b/services/core/java/com/android/server/am/Android.bp
index 0294ffe6e151..ceba01e48961 100644
--- a/services/core/java/com/android/server/am/Android.bp
+++ b/services/core/java/com/android/server/am/Android.bp
@@ -9,3 +9,10 @@ java_aconfig_library {
name: "am_flags_lib",
aconfig_declarations: "am_flags",
}
+
+java_aconfig_library {
+ name: "am_flags_host_lib",
+ host_supported: true,
+ libs: ["fake_device_config"],
+ aconfig_declarations: "am_flags",
+}
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 4a7ad31cba3e..1b00cec90bc4 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import static android.app.ApplicationStartInfo.START_TIMESTAMP_LAUNCH;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -51,6 +50,8 @@ import android.util.proto.WireTypeMismatchException;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ProcessMap;
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
import com.android.server.IoThread;
import com.android.server.ServiceThread;
import com.android.server.SystemServiceManager;
@@ -107,6 +108,16 @@ public final class AppStartInfoTracker {
@VisibleForTesting boolean mEnabled = false;
+ /**
+ * Monotonic clock which does not reset on reboot.
+ *
+ * Time for offset is persisted along with records, see {@link #persistProcessStartInfo}.
+ * This does not follow the recommendation of {@link MonotonicClock} to persist on shutdown as
+ * it's ok in this case to lose any time change past the last persist as records added since
+ * then will be lost as well and the purpose of this clock is to keep records in order.
+ */
+ @VisibleForTesting MonotonicClock mMonotonicClock = null;
+
/** Initialized in {@link #init} and read-only after that. */
@VisibleForTesting ActivityManagerService mService;
@@ -203,6 +214,15 @@ public final class AppStartInfoTracker {
IoThread.getHandler().post(() -> {
loadExistingProcessStartInfo();
});
+
+ if (mMonotonicClock == null) {
+ // This should only happen if there are no persisted records, or if the records were
+ // persisted by a version without the monotonic clock. Either way, create a new clock
+ // with no offset. In the case of records with no monotonic time the value will default
+ // to 0 and all new records will correctly end up in front of them.
+ mMonotonicClock = new MonotonicClock(Clock.SYSTEM_CLOCK.elapsedRealtime(),
+ Clock.SYSTEM_CLOCK);
+ }
}
/**
@@ -264,7 +284,7 @@ public final class AppStartInfoTracker {
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo();
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.setIntent(intent);
start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
@@ -396,7 +416,7 @@ public final class AppStartInfoTracker {
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo();
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
@@ -422,7 +442,7 @@ public final class AppStartInfoTracker {
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo();
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
@@ -444,7 +464,7 @@ public final class AppStartInfoTracker {
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo();
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
@@ -461,7 +481,7 @@ public final class AppStartInfoTracker {
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo();
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
@@ -632,7 +652,8 @@ public final class AppStartInfoTracker {
Collections.sort(
list, (a, b) ->
- Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+ Long.compare(b.getMonoticCreationTimeMs(),
+ a.getMonoticCreationTimeMs()));
int size = list.size();
if (maxNum > 0) {
size = Math.min(size, maxNum);
@@ -898,6 +919,10 @@ public final class AppStartInfoTracker {
case (int) AppsStartInfoProto.PACKAGES:
loadPackagesFromProto(proto, next);
break;
+ case (int) AppsStartInfoProto.MONOTONIC_TIME:
+ long monotonicTime = proto.readLong(AppsStartInfoProto.MONOTONIC_TIME);
+ mMonotonicClock = new MonotonicClock(monotonicTime, Clock.SYSTEM_CLOCK);
+ break;
}
}
} catch (IOException | IllegalArgumentException | WireTypeMismatchException
@@ -979,6 +1004,7 @@ public final class AppStartInfoTracker {
mLastAppStartInfoPersistTimestamp = now;
}
}
+ proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTime());
if (succeeded) {
proto.flush();
af.finishWrite(out);
@@ -1099,13 +1125,12 @@ public final class AppStartInfoTracker {
}
}
- /** Convenience method to obtain timestamp of beginning of start.*/
- private static long getStartTimestamp(ApplicationStartInfo startInfo) {
- if (startInfo.getStartupTimestamps() == null
- || !startInfo.getStartupTimestamps().containsKey(START_TIMESTAMP_LAUNCH)) {
- return -1;
+ private long getMonotonicTime() {
+ if (mMonotonicClock == null) {
+ // This should never happen. Return 0 to not interfere with past or future records.
+ return 0;
}
- return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
+ return mMonotonicClock.monotonicTime();
}
/** A container class of (@link android.app.ApplicationStartInfo) */
@@ -1143,7 +1168,7 @@ public final class AppStartInfoTracker {
// Sort records so we can remove the least recent ones.
Collections.sort(mInfos, (a, b) ->
- Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+ Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
// Remove records and trim list object back to size.
mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear();
@@ -1165,8 +1190,8 @@ public final class AppStartInfoTracker {
long oldestTimeStamp = Long.MAX_VALUE;
for (int i = 0; i < size; i++) {
ApplicationStartInfo startInfo = mInfos.get(i);
- if (getStartTimestamp(startInfo) < oldestTimeStamp) {
- oldestTimeStamp = getStartTimestamp(startInfo);
+ if (startInfo.getMonoticCreationTimeMs() < oldestTimeStamp) {
+ oldestTimeStamp = startInfo.getMonoticCreationTimeMs();
oldestIndex = i;
}
}
@@ -1176,7 +1201,7 @@ public final class AppStartInfoTracker {
}
mInfos.add(info);
Collections.sort(mInfos, (a, b) ->
- Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+ Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
}
/**
@@ -1337,7 +1362,9 @@ public final class AppStartInfoTracker {
mUid = proto.readInt(AppsStartInfoProto.Package.User.UID);
break;
case (int) AppsStartInfoProto.Package.User.APP_START_INFO:
- ApplicationStartInfo info = new ApplicationStartInfo();
+ // Create record with monotonic time 0 in case the persisted record does not
+ // have a create time.
+ ApplicationStartInfo info = new ApplicationStartInfo(0);
info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
mInfos.add(info);
break;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a7b2eb155558..8fe33d18152c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -929,9 +929,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// For ordered broadcast, check if the receivers for the new broadcast is a superset
// of those for the previous one as skipping and removing only one of them could result
// in an inconsistent state.
- if (testRecord.ordered || testRecord.prioritized) {
+ if (testRecord.ordered) {
return containsAllReceivers(r, testRecord, recordsLookupCache);
- } else if (testRecord.resultTo != null) {
+ } else if (testRecord.prioritized || testRecord.resultTo != null) {
return testRecord.getDeliveryState(testIndex) == DELIVERY_DEFERRED
? r.containsReceiver(testRecord.receivers.get(testIndex))
: containsAllReceivers(r, testRecord, recordsLookupCache);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 5d48d09adfbc..29373076c3b8 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -33,6 +33,7 @@ import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.providers.settings.Flags;
import android.aconfigd.Aconfigd.StorageRequestMessage;
import android.aconfigd.Aconfigd.StorageRequestMessages;
@@ -51,6 +52,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
+import java.util.Set;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
/**
@@ -457,6 +459,24 @@ public class SettingsToPropertiesMapper {
}
/**
+ * Send a request to aconfig storage to remove a flag local override.
+ *
+ * @param proto
+ * @param packageName the package of the flag
+ * @param flagName the name of the flag
+ */
+ static void writeFlagOverrideRemovalRequest(
+ ProtoOutputStream proto, String packageName, String flagName) {
+ long msgsToken = proto.start(StorageRequestMessages.MSGS);
+ long msgToken = proto.start(StorageRequestMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE);
+ proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.PACKAGE_NAME, packageName);
+ proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.FLAG_NAME, flagName);
+ proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_ALL, false);
+ proto.end(msgToken);
+ proto.end(msgsToken);
+ }
+
+ /**
* deserialize a flag input proto stream and log
* @param proto
*/
@@ -501,8 +521,15 @@ public class SettingsToPropertiesMapper {
ProtoOutputStream requests = new ProtoOutputStream();
for (String flagName : props.getKeyset()) {
String flagValue = props.getString(flagName, null);
- if (flagName == null || flagValue == null) {
- continue;
+
+ if (Flags.syncLocalOverridesRemovalNewStorage()) {
+ if (flagName == null) {
+ continue;
+ }
+ } else {
+ if (flagName == null || flagValue == null) {
+ continue;
+ }
}
int idx = flagName.indexOf(":");
@@ -519,7 +546,13 @@ public class SettingsToPropertiesMapper {
}
String packageName = fullFlagName.substring(0, idx);
String realFlagName = fullFlagName.substring(idx+1);
- writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
+
+ if (Flags.syncLocalOverridesRemovalNewStorage() && flagValue == null) {
+ writeFlagOverrideRemovalRequest(requests, packageName, realFlagName);
+ } else {
+ writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
+ }
+
++num_requests;
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index e4c65bd2147d..8c5152fdb0d6 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -2307,7 +2307,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
return;
}
- final int userId = mContext.getUserId();
+ final int userId = ActivityManager.getCurrentUser();
final boolean isNotGame = Arrays.stream(packages).noneMatch(
p -> isPackageGame(p, userId));
synchronized (mUidObserverLock) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ca907c57a858..1cf993521713 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1714,6 +1714,10 @@ public class AudioDeviceBroker {
sendIILMsg(MSG_IIL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, codec, address, delayMs);
}
+ /*package*/ void setHearingAidTimeout(String address, int delayMs) {
+ sendLMsg(MSG_IL_BT_HEARING_AID_TIMEOUT, SENDMSG_QUEUE, address, delayMs);
+ }
+
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
synchronized (mDeviceStateLock) {
mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
@@ -1959,6 +1963,13 @@ public class AudioDeviceBroker {
(String) msg.obj, msg.arg1, msg.arg2);
}
break;
+ case MSG_IL_BT_HEARING_AID_TIMEOUT:
+ // msg.obj == address of Hearing Aid device
+ synchronized (mDeviceStateLock) {
+ mDeviceInventory.onMakeHearingAidDeviceUnavailableNow(
+ (String) msg.obj);
+ }
+ break;
case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
final Pair<Integer, Boolean> codecAndChanged = mBtHelper.getCodecWithFallback(
@@ -2234,6 +2245,7 @@ public class AudioDeviceBroker {
private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58;
private static final int MSG_IL_UPDATED_ADI_DEVICE_STATE = 59;
private static final int MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE = 60;
+ private static final int MSG_IL_BT_HEARING_AID_TIMEOUT = 61;
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
@@ -2246,6 +2258,7 @@ public class AudioDeviceBroker {
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
case MSG_CHECK_MUTE_MUSIC:
+ case MSG_IL_BT_HEARING_AID_TIMEOUT:
return true;
default:
return false;
@@ -2330,6 +2343,7 @@ public class AudioDeviceBroker {
case MSG_IL_BTA2DP_TIMEOUT:
case MSG_IIL_BTLEAUDIO_TIMEOUT:
case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
+ case MSG_IL_BT_HEARING_AID_TIMEOUT:
if (sLastDeviceConnectMsgTime >= time) {
// add a little delay to make sure messages are ordered as expected
time = sLastDeviceConnectMsgTime + 30;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 8d8a54ea426d..a9bff8bf4bc3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1050,6 +1050,11 @@ public class AudioDeviceInventory {
}
}
+ /*package*/ void onMakeHearingAidDeviceUnavailableNow(String address) {
+ synchronized (mDevicesLock) {
+ makeHearingAidDeviceUnavailable(address);
+ }
+ }
/**
* Goes over all connected LE Audio devices in the provided group ID and
@@ -1457,7 +1462,7 @@ public class AudioDeviceInventory {
private int setDevicesRoleForCapturePreset(int capturePreset, int role,
@NonNull List<AudioDeviceAttributes> devices) {
return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
- return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
+ return mAudioSystem.setDevicesRoleForCapturePreset(p, r, d);
}, (p, r, d) -> {
return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
}, capturePreset, role, devices);
@@ -1902,12 +1907,10 @@ public class AudioDeviceInventory {
.set(MediaMetrics.Property.EVENT, "disconnectHearingAid")
.record();
if (toRemove.size() > 0) {
- /*final int delay = */
- checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
+ final int delay = checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
toRemove.stream().forEach(deviceAddress ->
- // TODO delay not used?
- makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
+ makeHearingAidDeviceUnavailableLater(deviceAddress, delay)
);
}
}
@@ -2498,6 +2501,15 @@ public class AudioDeviceInventory {
mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
+ @GuardedBy("mDevicesLock")
+ private void makeHearingAidDeviceUnavailableLater(
+ String address, int delayMs) {
+ // the device will be made unavailable later, so consider it disconnected right away
+ mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address));
+ // send the delayed message to make the device unavailable later
+ mDeviceBroker.setHearingAidTimeout(address, delayMs);
+ }
+
/**
* Returns whether a device of type DEVICE_OUT_HEARING_AID is connected.
* Visibility by APM plays no role
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index c5180afcce7d..5283eddd90fb 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -33,6 +33,7 @@ import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IntArray;
@@ -190,6 +191,7 @@ public class AudioServerPermissionProvider {
mIsUpdateDeferred = true;
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "audioserver_permission_update");
try {
for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
var newPerms = getUidsHoldingPerm(i);
@@ -203,6 +205,8 @@ public class AudioServerPermissionProvider {
mDest = null;
// We didn't necessarily finish
mIsUpdateDeferred = true;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53b04df4652e..14dca4e03b07 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -284,11 +284,16 @@ import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
@@ -785,6 +790,8 @@ public class AudioService extends IAudioService.Stub
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
private final Executor mAudioServerLifecycleExecutor;
+ private final ConcurrentLinkedQueue<Future> mScheduledPermissionTasks =
+ new ConcurrentLinkedQueue();
private IMediaProjectionManager mProjectionService; // to validate projection token
@@ -1092,7 +1099,8 @@ public class AudioService extends IAudioService.Stub
public Lifecycle(Context context) {
super(context);
- var audioserverLifecycleExecutor = Executors.newSingleThreadExecutor();
+ var audioserverLifecycleExecutor = Executors.newSingleThreadScheduledExecutor(
+ (Runnable r) -> new Thread(r, "audioserver_lifecycle"));
var audioPolicyFacade = new DefaultAudioPolicyFacade(audioserverLifecycleExecutor);
mService = new AudioService(context,
AudioSystemAdapter.getDefaultAdapter(),
@@ -1222,34 +1230,6 @@ public class AudioService extends IAudioService.Stub
mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
mBroadcastHandlerThread.start();
- // Listen to permission invalidations for the PermissionProvider
- if (audioserverPermissions()) {
- final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
- mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
- new Runnable() {
- // Roughly chosen to be long enough to suppress the autocork behavior
- // of the permission cache (50ms), and longer than the task could reasonably
- // take, even with many packages and users, while not introducing visible
- // permission leaks - since the app needs to restart, and trigger an action
- // which requires permissions from audioserver before this delay.
- // For RECORD_AUDIO, we are additionally protected by appops.
- final long UPDATE_DELAY_MS = 110;
- final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
- @Override
- public void run() {
- var currentTime = SystemClock.uptimeMillis();
- if (currentTime > scheduledUpdateTimestamp.get()) {
- scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
- broadcastHandler.postAtTime( () ->
- mAudioServerLifecycleExecutor.execute(mPermissionProvider
- ::onPermissionStateChanged),
- currentTime + UPDATE_DELAY_MS
- );
- }
- }
- });
- }
-
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -1717,8 +1697,10 @@ public class AudioService extends IAudioService.Stub
public void onSystemReady() {
mSystemReady = true;
+ if (audioserverPermissions()) {
+ setupPermissionListener();
+ }
scheduleLoadSoundEffects();
-
mDeviceBroker.onSystemReady();
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -10608,6 +10590,63 @@ public class AudioService extends IAudioService.Stub
}
}
+ /* Listen to permission invalidations for the PermissionProvider */
+ private void setupPermissionListener() {
+ // Roughly chosen to be long enough to suppress the autocork behavior of the permission
+ // cache (50ms), while not introducing visible permission leaks - since the app needs to
+ // restart, and trigger an action which requires permissions from audioserver before this
+ // delay. For RECORD_AUDIO, we are additionally protected by appops.
+ final long UPDATE_DELAY_MS = 60;
+ // instanceof to simplify the construction requirements of AudioService for testing: no
+ // delayed execution during unit tests.
+ if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) {
+ // We schedule and add from a this callback thread only (serially), so the task order on
+ // the serial executor matches the order on the task list. This list should almost
+ // always have only two elements, except in cases of serious system contention.
+ Runnable task = () -> mScheduledPermissionTasks.add(exec.schedule(() -> {
+ try {
+ // Clean up completed tasks before us to bound the queue length. Cancel any
+ // pending permission refresh tasks, after our own, since we are about to
+ // fulfill all of them. We must be the first non-completed task in the
+ // queue, since the execution order matches the queue order. Note, this
+ // task is the only writer on elements in the queue, and the task is
+ // serialized, so
+ // => no in-flight cancellation
+ // => exists at least one non-completed task (ourselves)
+ // => the queue is non-empty (only completed tasks removed)
+ final var iter = mScheduledPermissionTasks.iterator();
+ while (iter.next().isDone()) {
+ iter.remove();
+ }
+ // iter is on the first element which is not completed (us)
+ while (iter.hasNext()) {
+ if (!iter.next().cancel(false)) {
+ throw new AssertionError(
+ "Cancel should be infallible since we" +
+ "cancel from the executor");
+ }
+ iter.remove();
+ }
+ mPermissionProvider.onPermissionStateChanged();
+ } catch (Exception e) {
+ // Handle executor routing exceptions to nowhere
+ Thread.getDefaultUncaughtExceptionHandler()
+ .uncaughtException(Thread.currentThread(), e);
+ }
+ },
+ UPDATE_DELAY_MS,
+ TimeUnit.MILLISECONDS));
+ mAudioSystem.listenForSystemPropertyChange(
+ PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ task);
+ task.run();
+ } else {
+ mAudioSystem.listenForSystemPropertyChange(
+ PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ () -> mAudioServerLifecycleExecutor.execute(
+ mPermissionProvider::onPermissionStateChanged));
+ }
+ }
//==========================================================================================
// Audio Focus
@@ -10760,6 +10799,11 @@ public class AudioService extends IAudioService.Stub
final long token = Binder.clearCallingIdentity();
try {
+ //TODO move inside HardeningEnforcer after refactor that moves permission checks
+ // in the blockFocusMethod
+ if (permissionOverridesCheck) {
+ mHardeningEnforcer.metricsLogFocusReq(/*blocked*/false, durationHint, uid);
+ }
if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid,
HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS,
clientId, durationHint, callingPackageName, attributionTag, sdk)) {
@@ -14663,6 +14707,20 @@ public class AudioService extends IAudioService.Stub
return activeAssistantUids;
}
+ @Override
+ /** @see AudioManager#permissionUpdateBarrier() */
+ public void permissionUpdateBarrier() {
+ for (var x : List.copyOf(mScheduledPermissionTasks)) {
+ try {
+ x.get();
+ } catch (CancellationException e) {
+ // Task completed
+ } catch (InterruptedException | ExecutionException e) {
+ Log.wtf(TAG, "Exception which should never occur", e);
+ }
+ }
+ }
+
List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
return mDeviceBroker.getDeviceIdentityAddresses(device);
}
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
index 8ae04accb62f..3c509bca1b84 100644
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java
@@ -31,7 +31,9 @@ import android.os.Build;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
+import com.android.modules.expresslog.Counter;
import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
@@ -55,6 +57,30 @@ public class HardeningEnforcer {
final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS,
"Hardening enforcement");
+ // capacity = 4 for each of the focus request types
+ static final SparseArray<String> METRIC_COUNTERS_FOCUS_DENIAL = new SparseArray<>(4);
+ static final SparseArray<String> METRIC_COUNTERS_FOCUS_GRANT = new SparseArray<>(4);
+
+ static {
+ METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN,
+ "media_audio.value_audio_focus_gain_granted");
+ METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+ "media_audio.value_audio_focus_gain_transient_granted");
+ METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ "media_audio.value_audio_focus_gain_transient_duck_granted");
+ METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+ "media_audio.value_audio_focus_gain_transient_excl_granted");
+
+ METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN,
+ "media_audio.value_audio_focus_gain_appops_denial");
+ METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+ "media_audio.value_audio_focus_gain_transient_appops_denial");
+ METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ "media_audio.value_audio_focus_gain_transient_duck_appops_denial");
+ METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+ "media_audio.value_audio_focus_gain_transient_excl_appops_denial");
+ }
+
/**
* Matches calls from {@link AudioManager#setStreamVolume(int, int, int)}
*/
@@ -141,29 +167,49 @@ public class HardeningEnforcer {
packageName = getPackNameForUid(callingUid);
}
+ boolean blocked = true;
if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) {
if (DEBUG) {
Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking");
}
- return false;
+ blocked = false;
} else if (targetSdk < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
if (DEBUG) {
Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking due to sdk="
+ targetSdk);
}
+ blocked = false;
+ }
+
+ metricsLogFocusReq(blocked, durationHint, callingUid);
+
+ if (!blocked) {
return false;
}
String errorMssg = "Focus request DENIED for uid:" + callingUid
+ " clientId:" + clientId + " req:" + durationHint
+ " procState:" + mActivityManager.getUidProcessState(callingUid);
-
- // TODO metrics
mEventLogger.enqueueAndSlog(errorMssg, EventLogger.Event.ALOGI, TAG);
return true;
}
+ /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid) {
+ final String metricId = blocked ? METRIC_COUNTERS_FOCUS_DENIAL.get(focusReq)
+ : METRIC_COUNTERS_FOCUS_GRANT.get(focusReq);
+ if (TextUtils.isEmpty(metricId)) {
+ Slog.e(TAG, "Bad string for focus metrics gain:" + focusReq + " blocked:" + blocked);
+ return;
+ }
+ try {
+ Counter.logIncrementWithUid(metricId, callingUid);
+ } catch (Exception e) {
+ Slog.e(TAG, "Counter error metricId:" + metricId + " for focus req:" + focusReq
+ + " from uid:" + callingUid, e);
+ }
+ }
+
private String getPackNameForUid(int uid) {
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index cf677d541fb2..7b1186c9d4c7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -80,13 +80,16 @@ public class AidlResponseHandler extends ISessionCallback.Stub {
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @NonNull
+ private final FaceUtils mBiometricUtils;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
@NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback,
+ @NonNull FaceUtils biometricUtils) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
@@ -95,6 +98,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub {
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
+ mBiometricUtils = biometricUtils;
}
@Override
@@ -167,8 +171,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub {
} else {
currentUserId = client.getTargetUserId();
}
- final CharSequence name = FaceUtils.getInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
+ final CharSequence name = mBiometricUtils.getUniqueName(mContext, currentUserId);
final Face face = new Face(name, enrollmentId, mSensorId);
handleResponse(FaceEnrollClient.class, (c) -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 3eecc6de7450..d4ec573e1667 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -60,7 +60,6 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.face.FaceService;
-import com.android.server.biometrics.sensors.face.FaceUtils;
import java.io.IOException;
import java.util.ArrayList;
@@ -85,6 +84,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
private final int mMaxTemplatesPerUser;
private final boolean mDebugConsent;
private final @android.hardware.face.FaceEnrollOptions.EnrollReason int mEnrollReason;
+ private final BiometricUtils<Face> mBiometricUtils;
private final ClientMonitorCallback mPreviewHandleDeleterCallback =
new ClientMonitorCallback() {
@@ -107,7 +107,8 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
int maxTemplatesPerUser, boolean debugConsent,
android.hardware.face.FaceEnrollOptions options,
- @NonNull AuthenticationStateListeners authenticationStateListeners) {
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
+ @NonNull BiometricUtils<Face> biometricUtils) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
@@ -122,6 +123,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
mDebugConsent = debugConsent;
mDisabledFeatures = disabledFeatures;
mPreviewSurface = previewSurface;
+ mBiometricUtils = biometricUtils;
Slog.w(TAG, "EnrollOptions "
+ android.hardware.face.FaceEnrollOptions.enrollReasonToString(
options.getEnrollReason()));
@@ -144,7 +146,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
@Override
protected boolean hasReachedEnrollmentLimit() {
- return FaceUtils.getInstance(getSensorId()).getBiometricsForUser(getContext(),
+ return mBiometricUtils.getBiometricsForUser(getContext(),
getTargetUserId()).size() >= mMaxTemplatesPerUser;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index 964bf6cad63c..c27b7c483afc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -30,7 +30,6 @@ import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
import com.android.server.biometrics.sensors.RemovalClient;
-import com.android.server.biometrics.sensors.face.FaceUtils;
import java.util.List;
import java.util.Map;
@@ -75,7 +74,7 @@ public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlS
@Override
protected void onAddUnknownTemplate(int userId,
@NonNull BiometricAuthenticator.Identifier identifier) {
- FaceUtils.getInstance(getSensorId()).addBiometricForUser(
+ mBiometricUtils.addBiometricForUser(
getContext(), getTargetUserId(), (Face) identifier);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index f0a418951505..bb213bfa79e6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -72,7 +72,6 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
-import com.android.server.biometrics.sensors.face.FaceUtils;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter;
@@ -326,8 +325,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
}
if (Build.isDebuggable()) {
- BiometricUtils<Face> utils = FaceUtils.getInstance(
- mFaceSensors.keyAt(0));
+ BiometricUtils<Face> utils = mFaceSensors.get(
+ mFaceSensors.keyAt(0)).getFaceUtilsInstance();
for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
List<Face> enrollments = utils.getBiometricsForUser(mContext, user.id);
Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": "
@@ -386,7 +385,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
new InvalidationRequesterClient<>(mContext, userId, sensorId,
BiometricLogger.ofUnknown(mContext),
mBiometricContext,
- FaceUtils.getInstance(sensorId));
+ mFaceSensors.get(sensorId).getFaceUtilsInstance());
scheduleForSensor(sensorId, client);
});
}
@@ -415,7 +414,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@NonNull
@Override
public List<Face> getEnrolledFaces(int sensorId, int userId) {
- return FaceUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId);
+ return mFaceSensors.get(sensorId).getFaceUtilsInstance()
+ .getBiometricsForUser(mContext, userId);
}
@Override
@@ -497,13 +497,14 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
final FaceEnrollClient client = new FaceEnrollClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
+ opPackageName, id, mFaceSensors.get(sensorId).getFaceUtilsInstance(),
+ disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent, options,
- mAuthenticationStateListeners);
+ mAuthenticationStateListeners,
+ mFaceSensors.get(sensorId).getFaceUtilsInstance());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
return id;
@@ -615,7 +616,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@Override
public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- final List<Face> faces = FaceUtils.getInstance(sensorId)
+ final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
.getBiometricsForUser(mContext, userId);
final int[] faceIds = new int[faces.size()];
for (int i = 0; i < faces.size(); i++) {
@@ -632,7 +633,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
final FaceRemovalClient client = new FaceRemovalClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
- opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+ opPackageName, mFaceSensors.get(sensorId).getFaceUtilsInstance(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
@@ -666,7 +667,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
- final List<Face> faces = FaceUtils.getInstance(sensorId)
+ final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
Slog.w(getTag(), "Ignoring setFeature, no templates enrolled for user: " + userId);
@@ -687,7 +688,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
mHandler.post(() -> {
mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
- final List<Face> faces = FaceUtils.getInstance(sensorId)
+ final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
Slog.w(getTag(), "Ignoring getFeature, no templates enrolled for user: " + userId);
@@ -727,7 +728,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext,
- FaceUtils.getInstance(sensorId),
+ mFaceSensors.get(sensorId).getFaceUtilsInstance(),
mFaceSensors.get(sensorId).getAuthenticatorIds());
if (favorHalEnrollments) {
client.setFavorHalEnrollments();
@@ -768,7 +769,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
JSONArray sets = new JSONArray();
for (UserInfo user : UserManager.get(mContext).getUsers()) {
final int userId = user.getUserHandle().getIdentifier();
- final int c = FaceUtils.getInstance(sensorId)
+ final int c = mFaceSensors.get(sensorId).getFaceUtilsInstance()
.getBiometricsForUser(mContext, userId).size();
JSONObject set = new JSONObject();
set.put("id", userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index b0e7575689ba..6f9534993a3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -158,7 +158,7 @@ public class Sensor {
Slog.e(TAG, "Face sensor hardware unavailable.");
mCurrentSession = null;
}
- });
+ }, getFaceUtilsInstance());
return Sensor.this.getStartUserClient(resultController, sensorId,
newUserId, provider);
@@ -280,8 +280,7 @@ public class Sensor {
final long userToken = proto.start(SensorStateProto.USER_STATES);
proto.write(UserStateProto.USER_ID, userId);
proto.write(UserStateProto.NUM_ENROLLED,
- FaceUtils.getInstance(mSensorProperties.sensorId)
- .getBiometricsForUser(mContext, userId).size());
+ getFaceUtilsInstance().getBiometricsForUser(mContext, userId).size());
proto.end(userToken);
}
@@ -358,4 +357,8 @@ public class Sensor {
Supplier<AidlSession> lazySession) {
mLazySession = lazySession;
}
+
+ public FaceUtils getFaceUtilsInstance() {
+ return FaceUtils.getInstance(mSensorProperties.sensorId);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
index 9a4c29d7e978..444a6d18d27f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -159,6 +159,11 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe
}
@Override
+ public FaceUtils getFaceUtilsInstance() {
+ return FaceUtils.getLegacyInstance(getSensorProperties().sensorId);
+ }
+
+ @Override
protected LockoutTracker getLockoutTracker(boolean forAuth) {
return mLockoutTracker;
}
@@ -180,7 +185,8 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe
mLockoutTracker,
mLockoutResetDispatcher,
mAuthSessionCoordinator,
- mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback,
+ getFaceUtilsInstance());
}
private IBiometricsFace getIBiometricsFace() {
@@ -247,8 +253,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe
return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace,
mUserStartedCallback, userId, TAG, getSensorProperties().sensorId,
BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
- !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser(
- getContext(), userId).isEmpty(),
+ !getFaceUtilsInstance().getBiometricsForUser(getContext(), userId).isEmpty(),
getAuthenticatorIds());
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index 6d1715f1d500..80b7cde3124c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -80,13 +80,16 @@ public class AidlResponseHandler extends ISessionCallback.Stub {
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @NonNull
+ private final FingerprintUtils mBiometricUtils;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
@NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback,
+ @NonNull FingerprintUtils biometricUtils) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
@@ -95,6 +98,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub {
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
+ mBiometricUtils = biometricUtils;
}
@Override
@@ -158,8 +162,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub {
} else {
currentUserId = client.getTargetUserId();
}
- final CharSequence name = FingerprintUtils.getInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
+ final CharSequence name = mBiometricUtils.getUniqueName(mContext, currentUserId);
final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
enrollmentId, mSensorId);
handleResponse(FingerprintEnrollClient.class, (c) -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 1fc517906c58..40b8a45beb36 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -81,7 +81,7 @@ public class FingerprintInternalCleanupClient
@Override
protected void onAddUnknownTemplate(int userId,
@NonNull BiometricAuthenticator.Identifier identifier) {
- FingerprintUtils.getInstance(getSensorId()).addBiometricForUser(
+ mBiometricUtils.addBiometricForUser(
getContext(), getTargetUserId(), (Fingerprint) identifier);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 12baf00c1c4a..9edaa4e6d818 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -79,7 +79,6 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
@@ -354,8 +353,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
if (Build.isDebuggable()) {
- BiometricUtils<Fingerprint> utils = FingerprintUtils.getInstance(
- mFingerprintSensors.keyAt(0));
+ final int sensorId = mFingerprintSensors.keyAt(0);
+ final BiometricUtils<Fingerprint> utils = mFingerprintSensors.get(sensorId)
+ .getFingerprintUtilsInstance();
for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
List<Fingerprint> enrollments = utils.getBiometricsForUser(mContext, user.id);
Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": "
@@ -442,7 +442,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
new InvalidationRequesterClient<>(mContext, userId, sensorId,
BiometricLogger.ofUnknown(mContext),
mBiometricContext,
- FingerprintUtils.getInstance(sensorId));
+ mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance());
scheduleForSensor(sensorId, client);
});
}
@@ -507,7 +507,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FingerprintUtils.getInstance(sensorId),
+ opPackageName, mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
@@ -638,8 +638,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, int userId,
@NonNull String opPackageName) {
- final List<Fingerprint> fingers = FingerprintUtils.getInstance(sensorId)
- .getBiometricsForUser(mContext, userId);
+ final List<Fingerprint> fingers = mFingerprintSensors.get(sensorId)
+ .getFingerprintUtilsInstance().getBiometricsForUser(mContext, userId);
final int[] fingerIds = new int[fingers.size()];
for (int i = 0; i < fingers.size(); i++) {
fingerIds[i] = fingers.get(i).getBiometricId();
@@ -655,11 +655,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
- opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext,
+ opPackageName, mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
+ sensorId, createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector), mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -683,7 +682,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext,
- FingerprintUtils.getInstance(sensorId),
+ mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
if (favorHalEnrollments) {
client.setFavorHalEnrollments();
@@ -706,14 +705,15 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@Override
public void rename(int sensorId, int fingerId, int userId, @NonNull String name) {
- FingerprintUtils.getInstance(sensorId)
+ mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
.renameBiometricForUser(mContext, userId, fingerId, name);
}
@NonNull
@Override
public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) {
- return FingerprintUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId);
+ return mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
+ .getBiometricsForUser(mContext, userId);
}
@Override
@@ -842,7 +842,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
JSONArray sets = new JSONArray();
for (UserInfo user : UserManager.get(mContext).getUsers()) {
final int userId = user.getUserHandle().getIdentifier();
- final int c = FingerprintUtils.getInstance(sensorId)
+ final int c = mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
.getBiometricsForUser(mContext, userId).size();
JSONObject set = new JSONObject();
set.put("id", userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 1c6dfe0f5b24..d12d7b2dc89a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -170,7 +170,7 @@ public class Sensor {
"Fingerprint sensor hardware unavailable.");
mCurrentSession = null;
}
- });
+ }, getFingerprintUtilsInstance());
return Sensor.this.getStartUserClient(resultController, sensorId,
newUserId);
@@ -187,7 +187,7 @@ public class Sensor {
+ halInterfaceVersion);
mCurrentSession = new AidlSession(halInterfaceVersion,
newSession, userIdStarted, resultController);
- if (FingerprintUtils.getInstance(sensorId)
+ if (getFingerprintUtilsInstance()
.isInvalidationInProgress(mContext, userIdStarted)) {
Slog.w(TAG,
"Scheduling unfinished invalidation request for "
@@ -307,9 +307,8 @@ public class Sensor {
final long userToken = proto.start(SensorStateProto.USER_STATES);
proto.write(UserStateProto.USER_ID, userId);
- proto.write(UserStateProto.NUM_ENROLLED,
- FingerprintUtils.getInstance(mSensorProperties.sensorId)
- .getBiometricsForUser(mContext, userId).size());
+ proto.write(UserStateProto.NUM_ENROLLED, getFingerprintUtilsInstance()
+ .getBiometricsForUser(mContext, userId).size());
proto.end(userToken);
}
@@ -386,4 +385,8 @@ public class Sensor {
public FingerprintProvider getProvider() {
return mProvider;
}
+
+ public FingerprintUtils getFingerprintUtilsInstance() {
+ return FingerprintUtils.getInstance(mSensorProperties.sensorId);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 3214b6d3363f..8f52d00ad830 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -148,6 +148,11 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe
}
@Override
+ public FingerprintUtils getFingerprintUtilsInstance() {
+ return FingerprintUtils.getLegacyInstance(getSensorProperties().sensorId);
+ }
+
+ @Override
@Nullable
@VisibleForTesting
protected AidlSession getSessionForUser(int userId) {
@@ -186,7 +191,8 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe
mLockoutTracker,
mLockoutResetDispatcher,
mAuthSessionCoordinator,
- mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback,
+ getFingerprintUtilsInstance());
}
@VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
@@ -266,8 +272,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe
() -> getSession().getSession(), newUserId, TAG,
getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
getBiometricContext(), () -> mCurrentUserId,
- !FingerprintUtils.getInstance(getSensorProperties().sensorId)
- .getBiometricsForUser(getContext(),
+ !getFingerprintUtilsInstance().getBiometricsForUser(getContext(),
newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds,
mUserStartedCallback);
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index c31d1d8b271c..d909004e6381 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1500,10 +1500,18 @@ public class DisplayModeDirector {
}
private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) {
- Vote vote = info != null && info.layoutLimitedRefreshRate != null
- ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
- info.layoutLimitedRefreshRate.max) : null;
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
+ Vote refreshRateVote = null;
+ Vote frameRateVote = null;
+ if (info != null && info.layoutLimitedRefreshRate != null) {
+ refreshRateVote = Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
+ info.layoutLimitedRefreshRate.max);
+ frameRateVote = Vote.forRenderFrameRates(info.layoutLimitedRefreshRate.min,
+ info.layoutLimitedRefreshRate.max);
+ }
+ mVotesStorage.updateVote(
+ displayId, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE, refreshRateVote);
+ mVotesStorage.updateVote(
+ displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, frameRateVote);
}
private void removeUserSettingDisplayPreferredSize(int displayId) {
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 88ee044810db..459f9a6e8f13 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -110,37 +110,40 @@ interface Vote {
int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 13;
// For concurrent displays we want to limit refresh rate on all displays
- int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 14;
+ int PRIORITY_LAYOUT_LIMITED_REFRESH_RATE = 14;
+
+ // For concurrent displays we want to limit refresh rate on all displays
+ int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 15;
// For internal application to limit display modes to specific ids
- int PRIORITY_SYSTEM_REQUESTED_MODES = 15;
+ int PRIORITY_SYSTEM_REQUESTED_MODES = 16;
// PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if
// Settings.Global.LOW_POWER_MODE is on.
// Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other
// higher priority votes), render rate limit can still apply
- int PRIORITY_LOW_POWER_MODE_MODES = 16;
+ int PRIORITY_LOW_POWER_MODE_MODES = 17;
// PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if
// Settings.Global.LOW_POWER_MODE is on.
- int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 17;
+ int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 18;
// PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
// higher priority voters' result is a range, it will fix the rate to a single choice.
// It's used to avoid refresh rate switches in certain conditions which may result in the
// user seeing the display flickering when the switches occur.
- int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 18;
+ int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 19;
// Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
- int PRIORITY_SKIN_TEMPERATURE = 19;
+ int PRIORITY_SKIN_TEMPERATURE = 20;
// The proximity sensor needs the refresh rate to be locked in order to function, so this is
// set to a high priority.
- int PRIORITY_PROXIMITY = 20;
+ int PRIORITY_PROXIMITY = 21;
// The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
// to function, so this needs to be the highest priority of all votes.
- int PRIORITY_UDFPS = 21;
+ int PRIORITY_UDFPS = 22;
@IntDef(prefix = { "PRIORITY_" }, value = {
PRIORITY_DEFAULT_RENDER_FRAME_RATE,
@@ -157,6 +160,7 @@ interface Vote {
PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE,
PRIORITY_LIMIT_MODE,
PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE,
+ PRIORITY_LAYOUT_LIMITED_REFRESH_RATE,
PRIORITY_LAYOUT_LIMITED_FRAME_RATE,
PRIORITY_SYSTEM_REQUESTED_MODES,
PRIORITY_LOW_POWER_MODE_MODES,
@@ -283,6 +287,8 @@ interface Vote {
return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
+ case PRIORITY_LAYOUT_LIMITED_REFRESH_RATE:
+ return "PRIORITY_LAYOUT_LIMITED_REFRESH_RATE";
case PRIORITY_LAYOUT_LIMITED_FRAME_RATE:
return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE";
case PRIORITY_SYSTEM_REQUESTED_MODES:
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7ce9ee60d421..2ad0d2a1b658 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -326,7 +326,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
* Figures out the target IME user ID for a given {@link Binder} IPC.
*
* @param callingProcessUserId the user ID of the calling process
- * @return User ID to be used for this {@link Binder} call.
+ * @return the user ID to be used for this {@link Binder} call
*/
@GuardedBy("ImfLock.class")
@UserIdInt
@@ -336,6 +336,30 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
/**
+ * Figures out the targetIMuser for a given {@link Binder} IPC. In case
+ * {@code callingProcessUserId} is SYSTEM user, then it will return the owner of the display
+ * associated with the {@code client} passed as parameter.
+ *
+ * @param callingProcessUserId the user ID of the calling process
+ * @param client the input method client used to retrieve the user id in case
+ * {@code callingProcessUserId} is assigned to SYSTEM user
+ * @return the user ID to be used for this {@link Binder} call
+ */
+ @GuardedBy("ImfLock.class")
+ @UserIdInt
+ @BinderThread
+ private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId,
+ @NonNull IInputMethodClient client) {
+ if (mConcurrentMultiUserModeEnabled
+ && callingProcessUserId == UserHandle.USER_SYSTEM) {
+ final var clientState = mClientController.getClient(client.asBinder());
+ return mUserManagerInternal.getUserAssignedToDisplay(
+ clientState.mSelfReportedDisplayId);
+ }
+ return callingProcessUserId;
+ }
+
+ /**
* Figures out the target IME user ID associated with the given {@code displayId}.
*
* @param displayId the display ID to be queried about
@@ -3069,7 +3093,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
synchronized (ImfLock.class) {
final int uid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(uid);
- final int userId = resolveImeUserIdLocked(callingUserId);
+ final int userId = resolveImeUserIdLocked(callingUserId, client);
final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags,
lastClickToolType, resultReceiver, reason, uid, userId);
// When ZeroJankProxy is enabled, the app has already received "true" as the return
@@ -3515,7 +3539,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
synchronized (ImfLock.class) {
final int uid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(uid);
- final int userId = resolveImeUserIdLocked(callingUserId);
+ final int userId = resolveImeUserIdLocked(callingUserId, client);
final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags,
resultReceiver, reason, uid, userId);
// When ZeroJankProxy is enabled, the app has already received "true" as the return
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index d9e9e0021028..cf2cdc1500f8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -33,6 +33,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Printer;
import android.util.Slog;
@@ -115,7 +116,11 @@ final class InputMethodMenuControllerNew {
final var selectedImi = selectedIndex >= 0 ? items.get(selectedIndex).mImi : null;
final var languageSettingsIntent = selectedImi != null
? selectedImi.createImeLanguageSettingsActivityIntent() : null;
- final boolean hasLanguageSettingsButton = languageSettingsIntent != null;
+ final boolean isDeviceProvisioned = Settings.Global.getInt(
+ dialogWindowContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+ 0) != 0;
+ final boolean hasLanguageSettingsButton = languageSettingsIntent != null
+ && isDeviceProvisioned;
if (hasLanguageSettingsButton) {
final View buttonBar = contentView
.requireViewById(com.android.internal.R.id.button_bar);
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 008746c0423c..4fa711262a08 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -122,8 +122,9 @@ public class GroupHelper {
getNotificationShadeSections();
private static List<NotificationSectioner> getNotificationShadeSections() {
+ ArrayList<NotificationSectioner> sectionsList = new ArrayList<>();
if (android.service.notification.Flags.notificationClassification()) {
- return List.of(
+ sectionsList.addAll(List.of(
new NotificationSectioner("PromotionsSection", 0, (record) ->
NotificationChannel.PROMOTIONS_ID.equals(record.getChannel().getId())),
new NotificationSectioner("SocialSection", 0, (record) ->
@@ -131,18 +132,36 @@ public class GroupHelper {
new NotificationSectioner("NewsSection", 0, (record) ->
NotificationChannel.NEWS_ID.equals(record.getChannel().getId())),
new NotificationSectioner("RecsSection", 0, (record) ->
- NotificationChannel.RECS_ID.equals(record.getChannel().getId())),
- new NotificationSectioner("AlertingSection", 0, (record) ->
- record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
- new NotificationSectioner("SilentSection", 1, (record) ->
- record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
- } else {
- return List.of(
- new NotificationSectioner("AlertingSection", 0, (record) ->
- record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
- new NotificationSectioner("SilentSection", 1, (record) ->
- record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+ NotificationChannel.RECS_ID.equals(record.getChannel().getId()))));
}
+
+ if (Flags.notificationForceGroupConversations()) {
+ // add priority people section
+ sectionsList.add(new NotificationSectioner("PeopleSection(priority)", 1, (record) ->
+ record.isConversation() && record.getChannel().isImportantConversation()));
+
+ if (android.app.Flags.sortSectionByTime()) {
+ // add single people (alerting) section
+ sectionsList.add(new NotificationSectioner("PeopleSection", 0,
+ NotificationRecord::isConversation));
+ } else {
+ // add people alerting section
+ sectionsList.add(new NotificationSectioner("PeopleSection(alerting)", 1, (record) ->
+ record.isConversation()
+ && record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT));
+ // add people silent section
+ sectionsList.add(new NotificationSectioner("PeopleSection(silent)", 1, (record) ->
+ record.isConversation()
+ && record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+ }
+ }
+
+ sectionsList.addAll(List.of(
+ new NotificationSectioner("AlertingSection", 0, (record) ->
+ record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
+ new NotificationSectioner("SilentSection", 1, (record) ->
+ record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT)));
+ return sectionsList;
}
public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
@@ -830,25 +849,94 @@ public class GroupHelper {
}
}
+ // The list of notification operations required after the channel update
final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
- final Set<FullyQualifiedGroupKey> oldGroups =
- new HashSet<>(mAggregatedNotifications.keySet());
- for (FullyQualifiedGroupKey oldFullAggKey : oldGroups) {
+ // Check any already auto-grouped notifications that may need to be re-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getAutogroupedNotificationsMoveOps(userId, pkgName,
+ notificationsToCheck));
+
+ // Check any ungrouped notifications that may need to be auto-grouped
+ // after the channel update
+ notificationsToMove.addAll(
+ getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+
+ // Batch move to new section
+ if (!notificationsToMove.isEmpty()) {
+ moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
+ }
+ }
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private List<NotificationMoveOp> getAutogroupedNotificationsMoveOps(int userId, String pkgName,
+ ArrayMap<String, NotificationRecord> notificationsToCheck) {
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+ final Set<FullyQualifiedGroupKey> oldGroups =
+ new HashSet<>(mAggregatedNotifications.keySet());
+ // Move auto-grouped updated notifications from the old groups to the new groups (section)
+ for (FullyQualifiedGroupKey oldFullAggKey : oldGroups) {
+ // Only check aggregate groups that match the same userId & packageName
+ if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
+ final ArrayMap<String, NotificationAttributes> notificationsInAggGroup =
+ mAggregatedNotifications.get(oldFullAggKey);
+ if (notificationsInAggGroup == null) {
+ continue;
+ }
+
+ FullyQualifiedGroupKey newFullAggregateGroupKey = null;
+ for (String key : notificationsInAggGroup.keySet()) {
+ if (notificationsToCheck.get(key) != null) {
+ // check if section changes
+ NotificationSectioner sectioner = getSection(notificationsToCheck.get(key));
+ if (sectioner == null) {
+ continue;
+ }
+ newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
+ sectioner);
+ if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
+ if (DEBUG) {
+ Log.i(TAG, "Change section on channel update: " + key);
+ }
+ notificationsToMove.add(
+ new NotificationMoveOp(notificationsToCheck.get(key),
+ oldFullAggKey, newFullAggregateGroupKey));
+ notificationsToCheck.remove(key);
+ }
+ }
+ }
+ }
+ }
+ return notificationsToMove;
+ }
+
+ @GuardedBy("mAggregatedNotifications")
+ private List<NotificationMoveOp> getUngroupedNotificationsMoveOps(int userId, String pkgName,
+ final ArrayMap<String, NotificationRecord> notificationsToCheck) {
+ final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+ // Move any remaining ungrouped updated notifications from the old ungrouped list
+ // to the new ungrouped section list, if necessary
+ if (!notificationsToCheck.isEmpty()) {
+ final Set<FullyQualifiedGroupKey> oldUngroupedSectionKeys =
+ new HashSet<>(mUngroupedAbuseNotifications.keySet());
+ for (FullyQualifiedGroupKey oldFullAggKey : oldUngroupedSectionKeys) {
// Only check aggregate groups that match the same userId & packageName
if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
- final ArrayMap<String, NotificationAttributes> notificationsInAggGroup =
- mAggregatedNotifications.get(oldFullAggKey);
- if (notificationsInAggGroup == null) {
+ final ArrayMap<String, NotificationAttributes> ungroupedOld =
+ mUngroupedAbuseNotifications.get(oldFullAggKey);
+ if (ungroupedOld == null) {
continue;
}
FullyQualifiedGroupKey newFullAggregateGroupKey = null;
- for (String key : notificationsInAggGroup.keySet()) {
- if (notificationsToCheck.get(key) != null) {
+ final Set<String> ungroupedKeys = new HashSet<>(ungroupedOld.keySet());
+ for (String key : ungroupedKeys) {
+ NotificationRecord record = notificationsToCheck.get(key);
+ if (record != null) {
// check if section changes
- NotificationSectioner sectioner = getSection(
- notificationsToCheck.get(key));
+ NotificationSectioner sectioner = getSection(record);
if (sectioner == null) {
continue;
}
@@ -856,41 +944,22 @@ public class GroupHelper {
sectioner);
if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
if (DEBUG) {
- Log.i(TAG, "Change section on channel update: " + key);
+ Log.i(TAG, "Change ungrouped section: " + key);
}
notificationsToMove.add(
- new NotificationMoveOp(notificationsToCheck.get(key),
- oldFullAggKey, newFullAggregateGroupKey));
+ new NotificationMoveOp(record, oldFullAggKey,
+ newFullAggregateGroupKey));
+ notificationsToCheck.remove(key);
+ //Remove from previous ungrouped list
+ ungroupedOld.remove(key);
}
}
}
-
- if (newFullAggregateGroupKey != null) {
- // Add any notifications left ungrouped to the new section
- ArrayMap<String, NotificationAttributes> ungrouped =
- mUngroupedAbuseNotifications.get(newFullAggregateGroupKey);
- if (ungrouped != null) {
- for (NotificationRecord r : notificationList) {
- if (ungrouped.containsKey(r.getKey())) {
- if (DEBUG) {
- Log.i(TAG, "Add previously ungrouped: " + r);
- }
- notificationsToMove.add(
- new NotificationMoveOp(r, null, newFullAggregateGroupKey));
- }
- }
- //Cleanup mUngroupedAbuseNotifications
- mUngroupedAbuseNotifications.remove(newFullAggregateGroupKey);
- }
- }
+ mUngroupedAbuseNotifications.put(oldFullAggKey, ungroupedOld);
}
}
-
- // Batch move to new section
- if (!notificationsToMove.isEmpty()) {
- moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
- }
}
+ return notificationsToMove;
}
@GuardedBy("mAggregatedNotifications")
@@ -898,6 +967,7 @@ public class GroupHelper {
final List<NotificationMoveOp> notificationsToMove) {
record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
boolean hasSummary) { }
+ // Bundled operations to apply to groups affected by the channel update
ArrayMap<FullyQualifiedGroupKey, GroupUpdateOp> groupsToUpdate = new ArrayMap<>();
for (NotificationMoveOp moveOp: notificationsToMove) {
@@ -923,35 +993,36 @@ public class GroupHelper {
// Only add once, for triggering notification
if (!groupsToUpdate.containsKey(oldFullAggregateGroupKey)) {
groupsToUpdate.put(oldFullAggregateGroupKey,
- new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
+ new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
}
}
- // Add/update aggregate summary for new group
+ // Add moved notifications to the ungrouped list for new group and do grouping
+ // after all notifications have been handled
if (newFullAggregateGroupKey != null) {
final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
mAggregatedNotifications.getOrDefault(newFullAggregateGroupKey,
new ArrayMap<>());
- boolean newGroupExists = !newAggregatedNotificationsAttrs.isEmpty();
- newAggregatedNotificationsAttrs.put(record.getKey(),
- new NotificationAttributes(record.getFlags(),
- record.getNotification().getSmallIcon(),
- record.getNotification().color,
- record.getNotification().visibility,
- record.getNotification().getGroupAlertBehavior(),
- record.getChannel().getId()));
- mAggregatedNotifications.put(newFullAggregateGroupKey,
- newAggregatedNotificationsAttrs);
+ boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(newFullAggregateGroupKey,
+ new ArrayMap<>());
+ ungrouped.put(record.getKey(), new NotificationAttributes(
+ record.getFlags(),
+ record.getNotification().getSmallIcon(),
+ record.getNotification().color,
+ record.getNotification().visibility,
+ record.getNotification().getGroupAlertBehavior(),
+ record.getChannel().getId()));
+ mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
+
+ record.setOverrideGroupKey(null);
// Only add once, for triggering notification
if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
groupsToUpdate.put(newFullAggregateGroupKey,
- new GroupUpdateOp(newFullAggregateGroupKey, record, newGroupExists));
+ new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
}
-
- // Add notification to new group. do not request resort
- record.setOverrideGroupKey(null);
- mCallback.addAutoGroup(record.getKey(), newFullAggregateGroupKey.toString(), false);
}
}
@@ -959,18 +1030,26 @@ public class GroupHelper {
for (FullyQualifiedGroupKey groupKey : groupsToUpdate.keySet()) {
final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
mAggregatedNotifications.getOrDefault(groupKey, new ArrayMap<>());
- if (aggregatedNotificationsAttrs.isEmpty()) {
- mCallback.removeAutoGroupSummary(userId, pkgName, groupKey.toString());
- mAggregatedNotifications.remove(groupKey);
- } else {
- NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
- boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
+ final ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(groupKey, new ArrayMap<>());
+
+ NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
+ boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
+ //Group needs to be created/updated
+ if (ungrouped.size() >= mAutoGroupAtCount
+ || (hasSummary && !aggregatedNotificationsAttrs.isEmpty())) {
NotificationSectioner sectioner = getSection(triggeringNotification);
if (sectioner == null) {
continue;
}
- updateAggregateAppGroup(groupKey, triggeringNotification.getKey(), hasSummary,
- sectioner.mSummaryId);
+ aggregateUngroupedNotifications(groupKey, triggeringNotification.getKey(),
+ ungrouped, hasSummary, sectioner.mSummaryId);
+ } else {
+ // Remove empty groups
+ if (aggregatedNotificationsAttrs.isEmpty() && hasSummary) {
+ mCallback.removeAutoGroupSummary(userId, pkgName, groupKey.toString());
+ mAggregatedNotifications.remove(groupKey);
+ }
}
}
}
@@ -1327,8 +1406,10 @@ public class GroupHelper {
}
private boolean isNotificationGroupable(final NotificationRecord record) {
- if (record.isConversation()) {
- return false;
+ if (!Flags.notificationForceGroupConversations()) {
+ if (record.isConversation()) {
+ return false;
+ }
}
Notification notification = record.getSbn().getNotification();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index ee3f48d9bd3f..6ff0e04bca77 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -485,7 +485,7 @@ public class ZenModeHelper {
newConfig = mConfig.copy();
ZenRule rule = new ZenRule();
populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
- rule = maybeRestoreRemovedRule(newConfig, rule, automaticZenRule, origin);
+ rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
newConfig.automaticRules.put(rule.id, rule);
maybeReplaceDefaultRule(newConfig, automaticZenRule);
@@ -498,7 +498,7 @@ public class ZenModeHelper {
}
@GuardedBy("mConfigLock")
- private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
+ private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, String pkg, ZenRule ruleToAdd,
AutomaticZenRule azrToAdd, @ConfigOrigin int origin) {
if (!Flags.modesApi()) {
return ruleToAdd;
@@ -522,10 +522,18 @@ public class ZenModeHelper {
if (origin != ORIGIN_APP) {
return ruleToAdd; // Okay to create anew.
}
+ if (Flags.modesUi()) {
+ if (!Objects.equals(ruleToRestore.pkg, pkg)
+ || !Objects.equals(ruleToRestore.component, azrToAdd.getOwner())) {
+ // Apps are not allowed to change the owner via updateAutomaticZenRule(). Thus, if
+ // they have to, delete+add is their only option.
+ return ruleToAdd;
+ }
+ }
// "Preserve" the previous rule by considering the azrToAdd an update instead.
// Only app-modifiable fields will actually be modified.
- populateZenRule(ruleToRestore.pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
+ populateZenRule(pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
return ruleToRestore;
}
@@ -757,7 +765,9 @@ public class ZenModeHelper {
try {
ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
rule.name = applicationInfo.loadLabel(mPm).toString();
- rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
+ if (!Flags.modesUi()) {
+ rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
+ }
} catch (PackageManager.NameNotFoundException e) {
// Should not happen, since it's the app calling us (?)
Log.w(TAG, "Package not found for creating implicit zen rule");
@@ -1742,6 +1752,15 @@ public class ZenModeHelper {
manualRulePolicy.overwrittenWith(automaticRule.zenPolicy);
}
}
+
+ if (Flags.modesApi() && Flags.modesUi()
+ && config.version < ZenModeConfig.XML_VERSION_MODES_UI) {
+ // Clear icons from implicit rules. App icons are not suitable for some
+ // surfaces, so juse use a default (the user can select a different one).
+ if (ZenModeConfig.isImplicitRuleId(automaticRule.id)) {
+ automaticRule.iconResName = null;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 7265cff19077..aac2c404fd38 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -149,3 +149,10 @@ flag {
description: "This flag enables forced auto-grouping singleton groups"
bug: "336488844"
}
+
+flag {
+ name: "notification_force_group_conversations"
+ namespace: "systemui"
+ description: "This flag enables forced auto-grouping conversations"
+ bug: "336488844"
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index ada6659b4a94..1317866af006 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1415,8 +1415,13 @@ final class InstallPackageHelper {
+ " an sdk library <"
+ parsedPackage.getSdkLibraryName() + ">"
+ " without changing the versionMajor, but the"
- + " targetSdkVersion or minSdkVersion has changed."
- );
+ + " targetSdkVersion or minSdkVersion has changed:"
+ + " Old targetSdkVersion: " + oldTargetSdk
+ + " new targetSdkVersion: " + newTargetSdk
+ + " Old minSdkVersion: " + oldMinSdk
+ + " new minSdkVersion: " + newMinSdk
+ + " versionMajor: " + newVersionMajor
+ );
}
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4a2d36736bae..a683a8c54849 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,6 +25,7 @@ import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
@@ -2518,6 +2519,38 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
+ * This method validates whether calling user is valid in visible background users feature.
+ * Valid user is the current user or the system or in the same profile group as the current
+ * user. Visible background users are not valid calling users.
+ */
+ public static void enforceCurrentUserIfVisibleBackgroundEnabled(@UserIdInt int currentUserId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ return;
+ }
+ final int callingUserId = UserHandle.getCallingUserId();
+ if (DBG) {
+ Slog.d(LOG_TAG, "enforceValidCallingUser: callingUserId=" + callingUserId
+ + " isSystemUser=" + (callingUserId == USER_SYSTEM)
+ + " currentUserId=" + currentUserId
+ + " callingPid=" + Binder.getCallingPid()
+ + " callingUid=" + Binder.getCallingUid());
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (callingUserId != USER_SYSTEM && callingUserId != currentUserId
+ && !UserManagerService.getInstance()
+ .isSameProfileGroup(callingUserId, currentUserId)) {
+ throw new SecurityException(
+ "Invalid calling user on devices that enable visible background users. "
+ + "callingUserId=" + callingUserId + " currentUserId="
+ + currentUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
* Gets the current and target user ids as a {@link Pair}, calling
* {@link ActivityManagerInternal} directly (and without performing any permission check).
*
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ba3de33d865c..f96706e474be 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1076,16 +1076,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private void interceptPowerKeyUp(KeyEvent event, boolean canceled) {
// Inform the StatusBar; but do not allow it to consume the event.
sendSystemKeyToStatusBarAsync(event);
-
- final boolean handled = canceled || mPowerKeyHandled;
-
- if (!handled) {
- if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == 0) {
- // Abort possibly stuck animations only when power key up without long press case.
- mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe);
- }
- }
-
finishPowerKeyPress();
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 67f5f27b42eb..989c8a802b36 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -313,12 +313,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
}
/**
- * Hint to window manager that the user has started a navigation action that should
- * abort animations that have no timeout, in case they got stuck.
- */
- void triggerAnimationFailsafe();
-
- /**
* The keyguard showing state has changed
*/
void onKeyguardShowingAndNotOccludedChanged();
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 2f1641980784..46e779fb0c45 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -149,8 +149,7 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
// WiFi keeps an accumulated total of stats. Keep the last WiFi stats so we can compute a delta.
// (This is unlike Bluetooth, where BatteryStatsImpl is left responsible for taking the delta.)
@GuardedBy("mWorkerLock")
- private WifiActivityEnergyInfo mLastWifiInfo =
- new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
+ private WifiActivityEnergyInfo mLastWifiInfo = null;
/**
* Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
@@ -827,8 +826,18 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
return null;
}
+ /**
+ * Return a delta WifiActivityEnergyInfo from the last WifiActivityEnergyInfo passed to the
+ * method.
+ */
+ @VisibleForTesting
@GuardedBy("mWorkerLock")
- private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
+ public WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
+ if (mLastWifiInfo == null) {
+ // This is the first time WifiActivityEnergyInfo has been collected since system boot.
+ // Use this first WifiActivityEnergyInfo as the starting point for all accumulations.
+ mLastWifiInfo = latest;
+ }
final long timePeriodMs = latest.getTimeSinceBootMillis()
- mLastWifiInfo.getTimeSinceBootMillis();
final long lastScanMs = mLastWifiInfo.getControllerScanDurationMillis();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 1d3de574991e..b45651d7aafc 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1167,7 +1167,6 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int USB_DATA_CONNECTED = 2;
int mUsbDataState = USB_DATA_UNKNOWN;
- private static final int GPS_SIGNAL_QUALITY_NONE = 2;
int mGpsSignalQualityBin = -1;
final StopwatchTimer[] mGpsSignalQualityTimer =
new StopwatchTimer[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
@@ -5528,7 +5527,7 @@ public class BatteryStatsImpl extends BatteryStats {
mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
HistoryItem.STATE_GPS_ON_FLAG, uid, "gnss");
mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs,
- GPS_SIGNAL_QUALITY_NONE);
+ HistoryItem.GNSS_SIGNAL_QUALITY_NONE);
stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
mGpsSignalQualityBin = -1;
if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
@@ -15487,7 +15486,8 @@ public class BatteryStatsImpl extends BatteryStats {
final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
final long totalControllerActivityTimeMs =
computeBatteryRealtime(mClock.elapsedRealtime() * 1000, which) / 1000;
- final long sleepTimeMs = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs);
+ final long sleepTimeMs = Math.max(0,
+ totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs));
final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which);
final long monitoredRailChargeConsumedMaMs =
counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which);
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
index 393fa39cdff6..03df46aa1701 100644
--- a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
@@ -115,6 +115,12 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor {
mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
}
} else {
+ if (mInitiatingUid == Process.INVALID_UID) {
+ if (item.eventCode == (BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_FINISH)) {
+ mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
+ }
+ }
recordUsageDuration(mPowerStats, mInitiatingUid, item.time);
mInitiatingUid = Process.INVALID_UID;
if (!mEnergyConsumerSupported) {
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
index 572bde9b9266..0b287109cfa6 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
@@ -27,15 +27,15 @@ import com.android.internal.os.PowerStats;
import java.util.Arrays;
public class GnssPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
- private int mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
- private long mGnssSignalLevelTimestamp;
- private final long[] mGnssSignalDurations =
- new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
private static final GnssPowerStatsLayout sStatsLayout = new GnssPowerStatsLayout();
private final UsageBasedPowerEstimator[] mSignalLevelEstimators =
new UsageBasedPowerEstimator[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
private final boolean mUseSignalLevelEstimators;
private long[] mTmpDeviceStatsArray;
+ private int mGnssSignalLevel;
+ private long mGnssSignalLevelTimestamp;
+ private final long[] mGnssSignalDurations =
+ new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
public GnssPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
super(BatteryConsumer.POWER_COMPONENT_GNSS, uidResolver,
@@ -55,20 +55,33 @@ public class GnssPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
}
@Override
- protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
- if ((item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) == 0) {
- mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
- return STATE_OFF;
- }
+ void start(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ super.start(stats, timestampMs);
- noteGnssSignalLevel(item);
- return STATE_ON;
+ mGnssSignalLevelTimestamp = timestampMs;
+ mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+ Arrays.fill(mGnssSignalDurations, 0);
}
- private void noteGnssSignalLevel(BatteryStats.HistoryItem item) {
- int signalLevel = (item.states2 & BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
- >> BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
- if (signalLevel >= GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS) {
+ @Override
+ protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
+ return (item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) != 0
+ ? STATE_ON : STATE_OFF;
+ }
+
+ @Override
+ void noteStateChange(PowerComponentAggregatedPowerStats stats, BatteryStats.HistoryItem item) {
+ super.noteStateChange(stats, item);
+
+ int signalLevel;
+ if ((item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) != 0) {
+ signalLevel = (item.states2 & BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+ >> BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+ if (signalLevel >= GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS) {
+ // Default GNSS signal quality to GOOD for the purposes of power attribution
+ signalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD;
+ }
+ } else {
signalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
}
if (signalLevel == mGnssSignalLevel) {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index ac56043faa7d..b35a0a772ff2 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -71,6 +71,7 @@ import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
+import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
import static libcore.io.IoUtils.closeQuietly;
@@ -1388,14 +1389,22 @@ public class StatsPullAtomService extends SystemService {
@NonNull
private static NetworkStats removeEmptyEntries(NetworkStats stats) {
- NetworkStats ret = new NetworkStats(0, 1);
+ final ArrayList<NetworkStats.Entry> entries = new ArrayList<>();
for (NetworkStats.Entry e : stats) {
if (e.getRxBytes() != 0 || e.getRxPackets() != 0 || e.getTxBytes() != 0
|| e.getTxPackets() != 0 || e.getOperations() != 0) {
- ret = ret.addEntry(e);
+ entries.add(e);
}
}
- return ret;
+ if (isAddEntriesSupported()) {
+ return new NetworkStats(0, entries.size()).addEntries(entries);
+ } else {
+ NetworkStats outputStats = new NetworkStats(0L, 1);
+ for (NetworkStats.Entry e : entries) {
+ outputStats = outputStats.addEntry(e);
+ }
+ return outputStats;
+ }
}
private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
@@ -1720,11 +1729,19 @@ public class StatsPullAtomService extends SystemService {
@NonNull
private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
@NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
- NetworkStats ret = new NetworkStats(0, 1);
+ final ArrayList<NetworkStats.Entry> entries = new ArrayList();
for (NetworkStats.Entry e : stats) {
- ret = ret.addEntry(slicer.apply(e));
+ entries.add(slicer.apply(e));
+ }
+ if (isAddEntriesSupported()) {
+ return new NetworkStats(0, entries.size()).addEntries(entries);
+ } else {
+ NetworkStats outputStats = new NetworkStats(0L, 1);
+ for (NetworkStats.Entry e : entries) {
+ outputStats = outputStats.addEntry(e);
+ }
+ return outputStats;
}
- return ret;
}
private void registerWifiBytesTransferBackground() {
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
index de5885201ea4..0318bdd61473 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
@@ -24,6 +24,9 @@ import static android.net.NetworkStats.SET_ALL;
import android.app.usage.NetworkStats;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.stats.Flags;
+
+import java.util.ArrayList;
/**
* Utility methods for accessing {@link android.net.NetworkStats}.
@@ -35,12 +38,21 @@ public class NetworkStatsUtils {
*/
public static android.net.NetworkStats fromPublicNetworkStats(
NetworkStats publiceNetworkStats) {
- android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+ final ArrayList<android.net.NetworkStats.Entry> entries = new ArrayList<>();
while (publiceNetworkStats.hasNextBucket()) {
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
publiceNetworkStats.getNextBucket(bucket);
- final android.net.NetworkStats.Entry entry = fromBucket(bucket);
- stats = stats.addEntry(entry);
+ entries.add(fromBucket(bucket));
+ }
+ android.net.NetworkStats stats = new android.net.NetworkStats(0L, 1);
+ // The new API is only supported on devices running the mainline version of `NetworkStats`.
+ // It should always be used when available for memory efficiency.
+ if (isAddEntriesSupported()) {
+ stats = stats.addEntries(entries);
+ } else {
+ for (android.net.NetworkStats.Entry entry : entries) {
+ stats = stats.addEntry(entry);
+ }
}
return stats;
}
@@ -106,4 +118,8 @@ public class NetworkStatsUtils {
}
return 0;
}
+
+ public static boolean isAddEntriesSupported() {
+ return Flags.netstatsUseAddEntries();
+ }
}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index f360837e1d28..afea3038bcbb 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -1,6 +1,20 @@
package: "com.android.server.stats"
container: "system"
+# Note: To ensure compatibility across all release configurations, initiate the ramp-up process
+# only after the 'com.android.net.flags.netstats_add_entries' flag has been fully deployed.
+# This flag provides the necessary API from the Connectivity module.
+# The flag was added because the flag 'com.android.net.flags.netstats_add_entries' for API
+# is already being rolled out, and modifying behavior during an active rollout might
+# lead to unwanted issues.
+flag {
+ name: "netstats_use_add_entries"
+ namespace: "statsd"
+ description: "Use NetworkStats#addEntries to reduce memory footprint"
+ bug: "335680025"
+ is_fixed_read_only: true
+}
+
flag {
name: "add_mobile_bytes_transfer_by_proc_state_puller"
namespace: "statsd"
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 2faa68a9948f..09d2a0263f2e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -168,11 +168,6 @@ public interface StatusBarManagerInternal {
*/
void onDisplayReady(int displayId);
- /**
- * Notifies System UI whether the recents animation is running.
- */
- void onRecentsAnimationStateChanged(boolean running);
-
/** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7d812ee8c5cd..0fd59670436e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -699,17 +699,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void onRecentsAnimationStateChanged(boolean running) {
- IStatusBar bar = mBar;
- if (bar != null) {
- try {
- bar.onRecentsAnimationStateChanged(running);
- } catch (RemoteException ex) {}
- }
-
- }
-
- @Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
@Behavior int behavior, @InsetsType int requestedVisibleTypes,
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 480db25ec606..8e24e9f8a4c0 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -38,6 +38,7 @@ import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.UserHandle;
+import android.provider.Settings;
import android.service.tracing.TraceReportService;
import android.tracing.ITracingServiceProxy;
import android.tracing.TraceReportParams;
@@ -87,6 +88,8 @@ public class TracingServiceProxy extends SystemService {
TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING;
private static final int REPORT_SVC_COMM_ERROR =
TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR;
+ private static final String NOTIFY_SESSION_ENDED_SETTING = "should_notify_trace_session_ended";
+ private static final int ENABLED = 1;
private final Context mContext;
private final PackageManager mPackageManager;
@@ -97,10 +100,22 @@ public class TracingServiceProxy extends SystemService {
/**
* Notifies system tracing app that a tracing session has ended. sessionStolen is ignored,
* as trace sessions are no longer stolen and are always cloned instead.
+ * <p>
+ * Cases exist where user-flows besides Traceur's QS Tile may end long-trace sessions. In
+ * these cases, a Global int will be set to flag the upcoming notifyTraceSessionEnded call
+ * as purposely muted once.
*/
@Override
public void notifyTraceSessionEnded(boolean sessionStolen /* unused */) {
- TracingServiceProxy.this.notifyTraceur();
+ long identity = Binder.clearCallingIdentity();
+ if (Settings.Global.getInt(mContext.getContentResolver(),
+ NOTIFY_SESSION_ENDED_SETTING, ENABLED) == ENABLED) {
+ TracingServiceProxy.this.notifyTraceur();
+ } else {
+ Settings.Global.putInt(mContext.getContentResolver(), NOTIFY_SESSION_ENDED_SETTING,
+ ENABLED);
+ }
+ Binder.restoreCallingIdentity(identity);
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 65fc7b2c5c39..d10ef319e187 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -28,7 +29,10 @@ import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
+import android.view.InputDevice;
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.internal.vibrator.persistence.XmlParserException;
import com.android.internal.vibrator.persistence.XmlReader;
@@ -42,6 +46,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
+import java.util.Locale;
/**
* Class that loads custom {@link VibrationEffect} to be performed for each
@@ -92,105 +97,146 @@ final class HapticFeedbackCustomization {
private static final String ATTRIBUTE_ID = "id";
/**
- * Parses the haptic feedback vibration customization XML file for the device, and provides a
- * mapping of the customized effect IDs to their respective {@link VibrationEffect}s.
- *
- * <p>This is potentially expensive, so avoid calling repeatedly. One call is enough, and the
- * caller should process the returned mapping (if any) for further queries.
- *
- * @param res {@link Resources} object to be used for reading the device's resources.
- * @return a {@link SparseArray} that maps each customized haptic feedback effect ID to its
- * respective {@link VibrationEffect}, or {@code null}, if the device has not configured
- * a file for haptic feedback constants customization.
- * @throws {@link IOException} if an IO error occurs while parsing the customization XML.
- * @throws {@link CustomizationParserException} for any non-IO error that occurs when parsing
- * the XML, like an invalid XML content or an invalid haptic feedback constant.
+ * A {@link SparseArray} that maps each customized haptic feedback effect ID to its
+ * respective {@link VibrationEffect}. If this is empty, system's default vibration will be
+ * used.
*/
- @Nullable
- static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo)
- throws CustomizationParserException, IOException {
- try {
- return loadVibrationsInternal(res, vibratorInfo);
- } catch (VibrationXmlParser.ParseFailedException
- | XmlParserException
- | XmlPullParserException e) {
- throw new CustomizationParserException(
- "Error parsing haptic feedback customization file.", e);
- }
- }
+ @NonNull
+ private final SparseArray<VibrationEffect> mHapticCustomizations;
- @Nullable
- private static SparseArray<VibrationEffect> loadVibrationsInternal(
- Resources res, VibratorInfo vibratorInfo) throws
- CustomizationParserException,
- IOException,
- XmlParserException,
- XmlPullParserException {
+ /**
+ * A {@link SparseArray} similar to {@link mHapticCustomizations} but for rotary input source
+ * specific customization.
+ */
+ @NonNull
+ private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceRotary;
+
+ /**
+ * A {@link SparseArray} similar to {@link mHapticCustomizations} but for touch screen input
+ * source specific customization.
+ */
+ @NonNull
+ private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceTouchScreen;
+
+ HapticFeedbackCustomization(Resources res, VibratorInfo vibratorInfo) {
if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
- return null;
+ mHapticCustomizations = new SparseArray<>();
+ mHapticCustomizationsForSourceRotary = new SparseArray<>();
+ mHapticCustomizationsForSourceTouchScreen = new SparseArray<>();
+ return;
}
- // Old loading path that reads customization from file at dir defined by config.
- TypedXmlPullParser parser = readCustomizationFile(res);
- if (parser == null) {
- // When old loading path doesn't succeed, try loading customization from resources.
- parser = readCustomizationResources(res);
- }
- if (parser == null) {
- Slog.d(TAG, "No loadable haptic feedback customization.");
- return null;
+ // Load base customizations.
+ SparseArray<VibrationEffect> hapticCustomizations;
+ hapticCustomizations = loadCustomizedFeedbackVibrationFromFile(res, vibratorInfo);
+ if (hapticCustomizations.size() == 0) {
+ // Input source customized haptic feedback was directly added in res. So, no need to old
+ // loading path.
+ hapticCustomizations = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+ R.xml.haptic_feedback_customization);
}
+ mHapticCustomizations = hapticCustomizations;
- XmlUtils.beginDocument(parser, TAG_CONSTANTS);
- XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
- int rootDepth = parser.getDepth();
+ // Load customizations specified by input sources.
+ if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+ mHapticCustomizationsForSourceRotary =
+ loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+ R.xml.haptic_feedback_customization_source_rotary_encoder);
+ mHapticCustomizationsForSourceTouchScreen =
+ loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+ R.xml.haptic_feedback_customization_source_touchscreen);
+ } else {
+ mHapticCustomizationsForSourceRotary = new SparseArray<>();
+ mHapticCustomizationsForSourceTouchScreen = new SparseArray<>();
+ }
+ }
- SparseArray<VibrationEffect> mapping = new SparseArray<>();
- while (XmlReader.readNextTagWithin(parser, rootDepth)) {
- XmlValidator.checkStartTag(parser, TAG_CONSTANT);
- int customizationDepth = parser.getDepth();
+ @VisibleForTesting
+ HapticFeedbackCustomization(@NonNull SparseArray<VibrationEffect> hapticCustomizations,
+ @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceRotary,
+ @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceTouchScreen) {
+ mHapticCustomizations = hapticCustomizations;
+ mHapticCustomizationsForSourceRotary = hapticCustomizationsForSourceRotary;
+ mHapticCustomizationsForSourceTouchScreen = hapticCustomizationsForSourceTouchScreen;
+ }
- // Only attribute in tag is the `id` attribute.
- XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID);
- int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID);
- if (mapping.contains(effectId)) {
- throw new CustomizationParserException(
- "Multiple customizations found for effect " + effectId);
- }
+ @Nullable
+ VibrationEffect getEffect(int effectId) {
+ return mHapticCustomizations.get(effectId);
+ }
- // Move the parser one step into the `<constant>` tag.
- XmlValidator.checkParserCondition(
- XmlReader.readNextTagWithin(parser, customizationDepth),
- "Unsupported empty customization tag for effect " + effectId);
+ @Nullable
+ VibrationEffect getEffect(int effectId, int inputSource) {
+ VibrationEffect resultVibration = null;
+ if ((InputDevice.SOURCE_ROTARY_ENCODER & inputSource) != 0) {
+ resultVibration = mHapticCustomizationsForSourceRotary.get(effectId);
+ } else if ((InputDevice.SOURCE_TOUCHSCREEN & inputSource) != 0) {
+ resultVibration = mHapticCustomizationsForSourceTouchScreen.get(effectId);
+ }
+ if (resultVibration == null) {
+ resultVibration = mHapticCustomizations.get(effectId);
+ }
+ return resultVibration;
+ }
- ParsedVibration parsedVibration = VibrationXmlParser.parseElement(
- parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
- VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
- if (effect != null) {
- if (effect.getDuration() == Long.MAX_VALUE) {
- throw new CustomizationParserException(String.format(
- "Vibration for effect ID %d is repeating, which is not allowed as a"
- + " haptic feedback: %s", effectId, effect));
- }
- mapping.put(effectId, effect);
+ /**
+ * Parses the haptic feedback vibration customization XML file for the device whose directory is
+ * specified by config. See {@link R.string.config_hapticFeedbackCustomizationFile}.
+ *
+ * @return Return a mapping of the customized effect IDs to their respective
+ * {@link VibrationEffect}s.
+ */
+ @NonNull
+ private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromFile(
+ Resources res, VibratorInfo vibratorInfo) {
+ try {
+ TypedXmlPullParser parser = readCustomizationFile(res);
+ if (parser == null) {
+ Slog.d(TAG, "No loadable haptic feedback customization from file.");
+ return new SparseArray<>();
}
-
- XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth);
+ return parseVibrations(parser, vibratorInfo);
+ } catch (XmlPullParserException | XmlParserException | IOException e) {
+ Slog.e(TAG, "Error parsing haptic feedback customizations from file", e);
+ return new SparseArray<>();
}
+ }
- // Make checks that the XML ends well.
- XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth);
- XmlReader.readDocumentEndTag(parser);
-
- return mapping;
+ /**
+ * Parses the haptic feedback vibration customization XML resource for the device.
+ *
+ * @return Return a mapping of the customized effect IDs to their respective
+ * {@link VibrationEffect}s.
+ */
+ @NonNull
+ private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromRes(
+ Resources res, VibratorInfo vibratorInfo, int xmlResId) {
+ try {
+ TypedXmlPullParser parser = readCustomizationResources(res, xmlResId);
+ if (parser == null) {
+ Slog.d(TAG, "No loadable haptic feedback customization from res.");
+ return new SparseArray<>();
+ }
+ return parseVibrations(parser, vibratorInfo);
+ } catch (XmlPullParserException | XmlParserException | IOException e) {
+ Slog.e(TAG, "Error parsing haptic feedback customizations from res", e);
+ return new SparseArray<>();
+ }
}
// TODO(b/356412421): deprecate old path related files.
private static TypedXmlPullParser readCustomizationFile(Resources res)
throws XmlPullParserException {
- String customizationFile = res.getString(
- com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
+ String customizationFile;
+ try {
+ customizationFile = res.getString(
+ R.string.config_hapticFeedbackCustomizationFile);
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Customization file directory config not found.", e);
+ return null;
+ }
+
if (TextUtils.isEmpty(customizationFile)) {
return null;
}
@@ -211,13 +257,14 @@ final class HapticFeedbackCustomization {
return parser;
}
- private static TypedXmlPullParser readCustomizationResources(Resources res) {
+ @Nullable
+ private static TypedXmlPullParser readCustomizationResources(Resources res, int xmlResId) {
if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) {
return null;
}
final XmlResourceParser resParser;
try {
- resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization);
+ resParser = res.getXml(xmlResId);
} catch (Resources.NotFoundException e) {
Slog.e(TAG, "Haptic customization resource not found.", e);
return null;
@@ -226,16 +273,52 @@ final class HapticFeedbackCustomization {
return XmlUtils.makeTyped(resParser);
}
- /**
- * Represents an error while parsing a haptic feedback customization XML.
- */
- static final class CustomizationParserException extends Exception {
- private CustomizationParserException(String message) {
- super(message);
- }
+ @NonNull
+ private static SparseArray<VibrationEffect> parseVibrations(TypedXmlPullParser parser,
+ VibratorInfo vibratorInfo)
+ throws XmlPullParserException, IOException, XmlParserException {
+ XmlUtils.beginDocument(parser, TAG_CONSTANTS);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+ int rootDepth = parser.getDepth();
+
+ SparseArray<VibrationEffect> mapping = new SparseArray<>();
+ while (XmlReader.readNextTagWithin(parser, rootDepth)) {
+ XmlValidator.checkStartTag(parser, TAG_CONSTANT);
+ int customizationDepth = parser.getDepth();
- private CustomizationParserException(String message, Throwable cause) {
- super(message, cause);
+ // Only attribute in tag is the `id` attribute.
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID);
+ int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID);
+ if (mapping.contains(effectId)) {
+ Slog.e(TAG, "Multiple customizations found for effect " + effectId);
+ return new SparseArray<>();
+ }
+
+ // Move the parser one step into the `<constant>` tag.
+ XmlValidator.checkParserCondition(
+ XmlReader.readNextTagWithin(parser, customizationDepth),
+ "Unsupported empty customization tag for effect " + effectId);
+
+ ParsedVibration parsedVibration = VibrationXmlParser.parseElement(
+ parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+ VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
+ if (effect != null) {
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ Slog.e(TAG, String.format(Locale.getDefault(),
+ "Vibration for effect ID %d is repeating, which is not allowed as a"
+ + " haptic feedback: %s", effectId, effect));
+ return new SparseArray<>();
+ }
+ mapping.put(effectId, effect);
+ }
+
+ XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth);
}
+
+ // Make checks that the XML ends well.
+ XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth);
+ XmlReader.readDocumentEndTag(parser);
+
+ return mapping;
}
}
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 07610872b208..cae6b34d4c73 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -16,19 +16,17 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.VibratorInfo;
-import android.util.Slog;
-import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import com.android.internal.annotations.VisibleForTesting;
-import java.io.IOException;
import java.io.PrintWriter;
/**
@@ -52,39 +50,29 @@ public final class HapticFeedbackVibrationProvider {
private final boolean mHapticTextHandleEnabled;
// Vibrator effect for haptic feedback during boot when safe mode is enabled.
private final VibrationEffect mSafeModeEnabledVibrationEffect;
- // Haptic feedback vibration customizations specific to the device.
- // If present and valid, a vibration here will be used for an effect.
- // Otherwise, the system's default vibration will be used.
- @Nullable private final SparseArray<VibrationEffect> mHapticCustomizations;
- private float mKeyboardVibrationFixedAmplitude;
+ private final HapticFeedbackCustomization mHapticFeedbackCustomization;
- public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
- this(res, vibrator.getInfo());
- }
+ private float mKeyboardVibrationFixedAmplitude;
public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
- this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
+ this(res, vibratorInfo, new HapticFeedbackCustomization(res, vibratorInfo));
}
- @VisibleForTesting HapticFeedbackVibrationProvider(
- Resources res,
- VibratorInfo vibratorInfo,
- @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
+ @VisibleForTesting
+ HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo,
+ HapticFeedbackCustomization hapticFeedbackCustomization) {
mVibratorInfo = vibratorInfo;
mHapticTextHandleEnabled = res.getBoolean(
com.android.internal.R.bool.config_enableHapticTextHandle);
+ mHapticFeedbackCustomization = hapticFeedbackCustomization;
+
+ VibrationEffect safeModeVibration = mHapticFeedbackCustomization.getEffect(
+ HapticFeedbackConstants.SAFE_MODE_ENABLED);
+ mSafeModeEnabledVibrationEffect = safeModeVibration != null ? safeModeVibration
+ : VibrationSettings.createEffectFromResource(res,
+ com.android.internal.R.array.config_safeModeEnabledVibePattern);
- if (hapticCustomizations != null && hapticCustomizations.size() == 0) {
- hapticCustomizations = null;
- }
- mHapticCustomizations = hapticCustomizations;
- mSafeModeEnabledVibrationEffect =
- effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
- ? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED)
- : VibrationSettings.createEffectFromResource(
- res,
- com.android.internal.R.array.config_safeModeEnabledVibePattern);
mKeyboardVibrationFixedAmplitude = res.getFloat(
com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude);
if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) {
@@ -100,88 +88,40 @@ public final class HapticFeedbackVibrationProvider {
* @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
* the provided effect ID is not supported.
*/
- @Nullable public VibrationEffect getVibrationForHapticFeedback(int effectId) {
- switch (effectId) {
- case HapticFeedbackConstants.CONTEXT_CLICK:
- case HapticFeedbackConstants.GESTURE_END:
- case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
- case HapticFeedbackConstants.SCROLL_TICK:
- case HapticFeedbackConstants.SEGMENT_TICK:
- return getVibration(effectId, VibrationEffect.EFFECT_TICK);
-
- case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
- if (!mHapticTextHandleEnabled) {
- return null;
- }
- // fallthrough
- case HapticFeedbackConstants.CLOCK_TICK:
- case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
- return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK);
-
- case HapticFeedbackConstants.KEYBOARD_RELEASE:
- case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
- return getKeyboardVibration(effectId);
-
- case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
- case HapticFeedbackConstants.DRAG_CROSSING:
- return getVibration(
- effectId,
- VibrationEffect.EFFECT_TICK,
- /* fallbackForPredefinedEffect= */ false);
-
- case HapticFeedbackConstants.VIRTUAL_KEY:
- case HapticFeedbackConstants.EDGE_RELEASE:
- case HapticFeedbackConstants.CALENDAR_DATE:
- case HapticFeedbackConstants.CONFIRM:
- case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
- case HapticFeedbackConstants.GESTURE_START:
- case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
- case HapticFeedbackConstants.SCROLL_LIMIT:
- return getVibration(effectId, VibrationEffect.EFFECT_CLICK);
-
- case HapticFeedbackConstants.LONG_PRESS:
- case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
- case HapticFeedbackConstants.DRAG_START:
- case HapticFeedbackConstants.EDGE_SQUEEZE:
- return getVibration(effectId, VibrationEffect.EFFECT_HEAVY_CLICK);
-
- case HapticFeedbackConstants.REJECT:
- case HapticFeedbackConstants.BIOMETRIC_REJECT:
- return getVibration(effectId, VibrationEffect.EFFECT_DOUBLE_CLICK);
-
- case HapticFeedbackConstants.SAFE_MODE_ENABLED:
- return mSafeModeEnabledVibrationEffect;
-
- case HapticFeedbackConstants.ASSISTANT_BUTTON:
- return getAssistantButtonVibration();
-
- case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
- return getVibration(
- effectId,
- VibrationEffect.Composition.PRIMITIVE_TICK,
- /* primitiveScale= */ 0.4f,
- VibrationEffect.EFFECT_TEXTURE_TICK);
-
- case HapticFeedbackConstants.TOGGLE_ON:
- return getVibration(
- effectId,
- VibrationEffect.Composition.PRIMITIVE_TICK,
- /* primitiveScale= */ 0.5f,
- VibrationEffect.EFFECT_TICK);
-
- case HapticFeedbackConstants.TOGGLE_OFF:
- return getVibration(
- effectId,
- VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
- /* primitiveScale= */ 0.2f,
- VibrationEffect.EFFECT_TEXTURE_TICK);
+ @Nullable public VibrationEffect getVibration(int effectId) {
+ if (!isFeedbackConstantEnabled(effectId)) {
+ return null;
+ }
+ VibrationEffect customizedVibration = mHapticFeedbackCustomization.getEffect(effectId);
+ if (customizedVibration != null) {
+ return customizedVibration;
+ }
+ return getVibrationForHapticFeedback(effectId);
+ }
- case HapticFeedbackConstants.NO_HAPTICS:
- default:
- return null;
+ /**
+ * Provides the {@link VibrationEffect} for a given haptic feedback effect ID (provided in
+ * {@link HapticFeedbackConstants}).
+ *
+ * @param effectId the haptic feedback effect ID whose respective vibration we want to get.
+ * @param inputSource the {@link InputDevice.Source} that customizes the haptic feedback
+ * corresponding to the {@code effectId}.
+ * @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
+ * the provided effect ID is not supported.
+ */
+ @Nullable public VibrationEffect getVibration(int effectId, int inputSource) {
+ if (!isFeedbackConstantEnabled(effectId)) {
+ return null;
+ }
+ VibrationEffect customizedVibration = mHapticFeedbackCustomization.getEffect(effectId,
+ inputSource);
+ if (customizedVibration != null) {
+ return customizedVibration;
}
+ return getVibrationForHapticFeedback(effectId);
}
+ // TODO(b/354049335): handle input source customized VibrationAttributes.
/**
* Provides the {@link VibrationAttributes} that should be used for a haptic feedback.
*
@@ -255,61 +195,106 @@ public final class HapticFeedbackVibrationProvider {
pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
}
- private VibrationEffect getVibration(int effectId, int predefinedVibrationEffectId) {
- return getVibration(
- effectId, predefinedVibrationEffectId, /* fallbackForPredefinedEffect= */ true);
+ private boolean isFeedbackConstantEnabled(int effectId) {
+ return switch (effectId) {
+ case HapticFeedbackConstants.TEXT_HANDLE_MOVE -> mHapticTextHandleEnabled;
+ case HapticFeedbackConstants.NO_HAPTICS -> false;
+ default -> true;
+ };
}
/**
- * Returns the customized vibration for {@code hapticFeedbackId}, or
- * {@code predefinedVibrationEffectId} if a customization does not exist for the haptic
- * feedback.
- *
- * <p>If a customization does not exist and the default predefined effect is to be returned,
- * {@code fallbackForPredefinedEffect} will be used to decide whether or not to fallback
- * to a generic pattern if the predefined effect is not hardware supported.
- *
- * @see VibrationEffect#get(int, boolean)
+ * Get {@link VibrationEffect} respective {@code effectId} from platform-wise mapping. This
+ * method doesn't include OEM customizations.
*/
- private VibrationEffect getVibration(
- int hapticFeedbackId,
- int predefinedVibrationEffectId,
- boolean fallbackForPredefinedEffect) {
- if (effectHasCustomization(hapticFeedbackId)) {
- return mHapticCustomizations.get(hapticFeedbackId);
+ @Nullable
+ private VibrationEffect getVibrationForHapticFeedback(int effectId) {
+ switch (effectId) {
+ case HapticFeedbackConstants.CONTEXT_CLICK:
+ case HapticFeedbackConstants.GESTURE_END:
+ case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
+ case HapticFeedbackConstants.SCROLL_TICK:
+ case HapticFeedbackConstants.SEGMENT_TICK:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+
+ case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
+ case HapticFeedbackConstants.CLOCK_TICK:
+ case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.KEYBOARD_RELEASE:
+ case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
+ // keyboard effect is not customized by the input source.
+ return getKeyboardVibration(effectId);
+
+ case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
+ case HapticFeedbackConstants.DRAG_CROSSING:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK, /* fallback= */ false);
+
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ case HapticFeedbackConstants.EDGE_RELEASE:
+ case HapticFeedbackConstants.CALENDAR_DATE:
+ case HapticFeedbackConstants.CONFIRM:
+ case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
+ case HapticFeedbackConstants.GESTURE_START:
+ case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+ case HapticFeedbackConstants.SCROLL_LIMIT:
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+ case HapticFeedbackConstants.LONG_PRESS:
+ case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+ case HapticFeedbackConstants.DRAG_START:
+ case HapticFeedbackConstants.EDGE_SQUEEZE:
+ return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+
+ case HapticFeedbackConstants.REJECT:
+ case HapticFeedbackConstants.BIOMETRIC_REJECT:
+ return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+
+ case HapticFeedbackConstants.SAFE_MODE_ENABLED:
+ // safe mode effect is not customized by the input source.
+ return mSafeModeEnabledVibrationEffect;
+
+ case HapticFeedbackConstants.ASSISTANT_BUTTON:
+ // assistant effect is not customized by the input source.
+ return getAssistantButtonVibration();
+
+ case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
+ return getVibration(
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ /* primitiveScale= */ 0.4f,
+ VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.TOGGLE_ON:
+ return getVibration(
+ VibrationEffect.Composition.PRIMITIVE_TICK,/* primitiveScale= */ 0.5f,
+ VibrationEffect.EFFECT_TICK);
+
+ case HapticFeedbackConstants.TOGGLE_OFF:
+ return getVibration(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ /* primitiveScale= */ 0.2f,
+ VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.NO_HAPTICS:
+ default:
+ return null;
}
- return VibrationEffect.get(predefinedVibrationEffectId, fallbackForPredefinedEffect);
}
- /**
- * Returns the customized vibration for {@code hapticFeedbackId}, or some fallback vibration if
- * a customization does not exist for the ID.
- *
- * <p>The fallback will be a primitive composition formed of {@code primitiveId} and
- * {@code primitiveScale}, if the primitive is supported. Otherwise, it will be a predefined
- * vibration of {@code elsePredefinedVibrationEffectId}.
- */
- private VibrationEffect getVibration(
- int hapticFeedbackId,
- int primitiveId,
- float primitiveScale,
- int elsePredefinedVibrationEffectId) {
- if (effectHasCustomization(hapticFeedbackId)) {
- return mHapticCustomizations.get(hapticFeedbackId);
- }
+ @NonNull
+ private VibrationEffect getVibration(int primitiveId, float primitiveScale,
+ int predefinedVibrationEffectId) {
if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
return VibrationEffect.startComposition()
.addPrimitive(primitiveId, primitiveScale)
.compose();
- } else {
- return VibrationEffect.get(elsePredefinedVibrationEffectId);
}
+ return VibrationEffect.get(predefinedVibrationEffectId);
}
+ @NonNull
private VibrationEffect getAssistantButtonVibration() {
- if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) {
- return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON);
- }
if (mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
&& mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)) {
// quiet ramp, short pause, then sharp tick
@@ -322,15 +307,8 @@ public final class HapticFeedbackVibrationProvider {
return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
}
- private boolean effectHasCustomization(int effectId) {
- return mHapticCustomizations != null && mHapticCustomizations.contains(effectId);
- }
-
+ @NonNull
private VibrationEffect getKeyboardVibration(int effectId) {
- if (effectHasCustomization(effectId)) {
- return mHapticCustomizations.get(effectId);
- }
-
int primitiveId;
int predefinedEffectId;
boolean predefinedEffectFallback;
@@ -354,8 +332,7 @@ public final class HapticFeedbackVibrationProvider {
.compose();
}
}
- return getVibration(effectId, predefinedEffectId,
- /* fallbackForPredefinedEffect= */ predefinedEffectFallback);
+ return VibrationEffect.get(predefinedEffectId, predefinedEffectFallback);
}
private VibrationAttributes createKeyboardVibrationAttributes(
@@ -367,17 +344,6 @@ public final class HapticFeedbackVibrationProvider {
return IME_FEEDBACK_VIBRATION_ATTRIBUTES;
}
- @Nullable
- private static SparseArray<VibrationEffect> loadHapticCustomizations(
- Resources res, VibratorInfo vibratorInfo) {
- try {
- return HapticFeedbackCustomization.loadVibrations(res, vibratorInfo);
- } catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
- Slog.e(TAG, "Unable to load haptic customizations.", e);
- return null;
- }
- }
-
private static boolean shouldBypassInterruptionPolicy(int effectId) {
switch (effectId) {
case HapticFeedbackConstants.SCROLL_TICK:
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index dd16d2433a64..c143bebdd4a5 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -487,39 +487,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
HalVibration performHapticFeedbackInternal(
int uid, int deviceId, String opPkg, int constant, String reason,
IBinder token, int flags, int privFlags) {
-
// Make sure we report the constant id in the requested haptic feedback reason.
reason = "performHapticFeedback(constant=" + constant + "): " + reason;
-
HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
- if (hapticVibrationProvider == null) {
- Slog.e(TAG, "performHapticFeedback; haptic vibration provider not ready.");
- logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
- Vibration.Status.IGNORED_ERROR_SCHEDULING);
- return null;
- }
-
- if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
- && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
- Slog.w(TAG, "performHapticFeedback; no permission for system constant " + constant);
- logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
- Vibration.Status.IGNORED_MISSING_PERMISSION);
+ Vibration.Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason,
+ hapticVibrationProvider);
+ if (ignoreStatus != null) {
+ logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason, ignoreStatus);
return null;
}
-
- VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant);
- if (effect == null) {
- Slog.w(TAG, "performHapticFeedback; vibration absent for constant " + constant);
- logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
- Vibration.Status.IGNORED_UNSUPPORTED);
- return null;
- }
-
- CombinedVibration vib = CombinedVibration.createParallel(effect);
- VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
- constant, flags, privFlags);
- VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
- return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
+ return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token,
+ hapticVibrationProvider.getVibration(constant),
+ hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+ constant, flags, privFlags));
}
/**
@@ -532,12 +512,35 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
HalVibration performHapticFeedbackForInputDeviceInternal(
int uid, int deviceId, String opPkg, int constant, int inputDeviceId, int inputSource,
String reason, IBinder token, int flags, int privFlags) {
- // TODO(b/355543835): implement input device specific logic.
- if (DEBUG) {
- Slog.d(TAG, "performHapticFeedbackForInput: input device specific not implemented.");
+ // Make sure we report the constant id in the requested haptic feedback reason.
+ reason = "performHapticFeedbackForInputDevice(constant=" + constant + ", inputDeviceId="
+ + inputDeviceId + ", inputSource=" + inputSource + "): " + reason;
+ HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
+ Vibration.Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason,
+ hapticVibrationProvider);
+ if (ignoreStatus != null) {
+ logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason, ignoreStatus);
+ return null;
}
- return performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
- this, flags, privFlags);
+ return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token,
+ hapticVibrationProvider.getVibration(constant, inputSource),
+ hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+ constant, flags, privFlags));
+ }
+
+ private HalVibration performHapticFeedbackWithEffect(int uid, int deviceId, String opPkg,
+ int constant, String reason, IBinder token, VibrationEffect effect,
+ VibrationAttributes attrs) {
+ if (effect == null) {
+ logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
+ Vibration.Status.IGNORED_UNSUPPORTED);
+ Slog.w(TAG,
+ "performHapticFeedbackWithEffect; vibration absent for constant " + constant);
+ return null;
+ }
+ CombinedVibration vib = CombinedVibration.createParallel(effect);
+ VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
+ return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
}
/**
@@ -1237,6 +1240,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return null;
}
+ @Nullable
+ private Vibration.Status shouldIgnoreHapticFeedback(int constant, String reason,
+ HapticFeedbackVibrationProvider hapticVibrationProvider) {
+ if (hapticVibrationProvider == null) {
+ Slog.e(TAG, reason + "; haptic vibration provider not ready.");
+ return Vibration.Status.IGNORED_ERROR_SCHEDULING;
+ }
+ if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
+ && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
+ Slog.w(TAG, reason + "; no permission for system constant " + constant);
+ return Vibration.Status.IGNORED_MISSING_PERMISSION;
+ }
+ return null;
+ }
+
/**
* Return true if the vibration has the same token and usage belongs to given usage class.
*
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 10ce8c273736..7cbacd6b0b82 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1764,15 +1764,7 @@ final class AccessibilityController {
IBinder topFocusedWindowToken = null;
synchronized (mService.mGlobalLock) {
- // If there is a recents animation running, then use the animation target as the
- // top window state. Otherwise,do not send the windows if there is no top focus as
- // the window manager is still looking for where to put it. We will do the work when
- // we get a focus change callback.
- final RecentsAnimationController controller =
- mService.getRecentsAnimationController();
- final WindowState topFocusedWindowState = controller != null
- ? controller.getTargetAppMainWindow()
- : getTopFocusWindow();
+ final WindowState topFocusedWindowState = getTopFocusWindow();
if (topFocusedWindowState == null) {
if (DEBUG) {
Slog.d(LOG_TAG, "top focused window is null, compute it again later");
@@ -1907,10 +1899,6 @@ final class AccessibilityController {
private boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow,
Region regionInScreen, Region unaccountedSpace) {
- if (a11yWindow.ignoreRecentsAnimationForAccessibility()) {
- return false;
- }
-
if (a11yWindow.isFocused()) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index d8e7c77309cd..fd2a909f8b05 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -659,7 +659,6 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
private boolean mIsPIPMenu;
private boolean mIsFocused;
private boolean mShouldMagnify;
- private boolean mIgnoreDuetoRecentsAnimation;
private final Region mTouchableRegionInScreen = new Region();
private final Region mTouchableRegionInWindow = new Region();
private WindowInfo mWindowInfo;
@@ -692,10 +691,6 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
instance.mIsFocused = windowState != null && windowState.isFocused();
instance.mShouldMagnify = windowState == null || windowState.shouldMagnify();
- final RecentsAnimationController controller = service.getRecentsAnimationController();
- instance.mIgnoreDuetoRecentsAnimation = windowState != null && controller != null
- && controller.shouldIgnoreForAccessibility(windowState);
-
final Rect windowFrame = new Rect(inputWindowHandle.frame);
getTouchableRegionInWindow(instance.mShouldMagnify, inputWindowHandle.touchableRegion,
instance.mTouchableRegionInWindow, windowFrame, magnificationInverseMatrix,
@@ -793,13 +788,6 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
}
/**
- * @return true if it's running the recent animation but not the target app.
- */
- public boolean ignoreRecentsAnimationForAccessibility() {
- return mIgnoreDuetoRecentsAnimation;
- }
-
- /**
* @return true if this window is the trusted overlay.
*/
public boolean isTrustedOverlay() {
@@ -909,7 +897,6 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
+ ", privateFlag=0x" + Integer.toHexString(mPrivateFlags)
+ ", focused=" + mIsFocused
+ ", shouldMagnify=" + mShouldMagnify
- + ", ignoreDuetoRecentsAnimation=" + mIgnoreDuetoRecentsAnimation
+ ", isTrustedOverlay=" + isTrustedOverlay()
+ ", regionInScreen=" + mTouchableRegionInScreen
+ ", touchableRegion=" + mTouchableRegionInWindow
diff --git a/services/core/java/com/android/server/wm/ActionChain.java b/services/core/java/com/android/server/wm/ActionChain.java
new file mode 100644
index 000000000000..c697d33ed4e2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActionChain.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Represents a chain of WM actions where each action is "caused by" the prior action (except the
+ * first one of course). A whole chain is associated with one Transition (in fact, the purpose
+ * of this object is to communicate, to all callees, which transition they are part of).
+ *
+ * A single action is defined as "one logical thing requested of WM". This usually corresponds to
+ * each ingress-point into the process. For example, when starting an activity:
+ * * the first action is to pause the current/top activity.
+ * At this point, control leaves the process while the activity pauses.
+ * * Then WM receives completePause (a new ingress). This is a new action that gets linked
+ * to the prior action. This action involves resuming the next activity, at which point,
+ * control leaves the process again.
+ * * Eventually, when everything is done, we will have formed a chain of actions.
+ *
+ * We don't technically need to hold onto each prior action in the chain once a new action has
+ * been linked to the same transition; however, keeping the whole chain enables improved
+ * debugging and the ability to detect anomalies.
+ */
+public class ActionChain {
+ private static final String TAG = "TransitionChain";
+
+ /**
+ * Normal link type. This means the action was expected and is properly linked to the
+ * current chain.
+ */
+ static final int TYPE_NORMAL = 0;
+
+ /**
+ * This is the "default" link. It means we haven't done anything to properly track this case
+ * so it may or may not be correct. It represents the behavior as if there was no tracking.
+ *
+ * Any type that has "default" behavior uses the global "collecting transition" if it exists,
+ * otherwise it doesn't use any transition.
+ */
+ static final int TYPE_DEFAULT = 1;
+
+ /**
+ * This means the action was performed via a legacy code-path. These should be removed
+ * eventually. This will have the "default" behavior.
+ */
+ static final int TYPE_LEGACY = 2;
+
+ /** This is for a test. */
+ static final int TYPE_TEST = 3;
+
+ /** This is finishing a transition. Collection isn't supported during this. */
+ static final int TYPE_FINISH = 4;
+
+ /**
+ * Something unexpected happened so this action was started to recover from the unexpected
+ * state. This means that a "real" chain-link couldn't be determined. For now, the behavior of
+ * this is the same as "default".
+ */
+ static final int TYPE_FAILSAFE = 5;
+
+ /**
+ * Types of chain links (ie. how is this action associated with the chain it is linked to).
+ * @hide
+ */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_NORMAL,
+ TYPE_DEFAULT,
+ TYPE_LEGACY,
+ TYPE_TEST,
+ TYPE_FINISH,
+ TYPE_FAILSAFE
+ })
+ public @interface LinkType {}
+
+ /** Identifies the entry-point of this action. */
+ @NonNull
+ final String mSource;
+
+ /** Reference to ATMS. TEMPORARY! ONLY USE THIS WHEN tracker_plumbing flag is DISABLED! */
+ @Nullable
+ ActivityTaskManagerService mTmpAtm;
+
+ /** The transition that this chain's changes belong to. */
+ @Nullable
+ Transition mTransition;
+
+ /** The previous action in the chain. */
+ @Nullable
+ ActionChain mPrevious = null;
+
+ /** Classification of how this action is connected to the chain. */
+ @LinkType int mType = TYPE_NORMAL;
+
+ /** When this Action started. */
+ long mCreateTimeMs;
+
+ private ActionChain(String source, @LinkType int type, Transition transit) {
+ mSource = source;
+ mCreateTimeMs = System.currentTimeMillis();
+ mType = type;
+ mTransition = transit;
+ if (mTransition != null) {
+ mTransition.recordChain(this);
+ }
+ }
+
+ private Transition getTransition() {
+ if (!Flags.transitTrackerPlumbing()) {
+ return mTmpAtm.getTransitionController().getCollectingTransition();
+ }
+ return mTransition;
+ }
+
+ boolean isFinishing() {
+ return mType == TYPE_FINISH;
+ }
+
+ /**
+ * Some common checks to determine (and report) whether this chain has a collecting transition.
+ */
+ private boolean expectCollecting() {
+ if (getTransition() == null) {
+ Slog.e(TAG, "Can't collect into a chain with no transition");
+ return false;
+ }
+ if (isFinishing()) {
+ Slog.e(TAG, "Trying to collect into a finished transition");
+ return false;
+ }
+ if (mTransition.mController.getCollectingTransition() != mTransition) {
+ Slog.e(TAG, "Mismatch between current collecting ("
+ + mTransition.mController.getCollectingTransition() + ") and chain ("
+ + mTransition + ")");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Helper to collect a container into the associated transition. This will automatically do
+ * nothing if the chain isn't associated with a collecting transition.
+ */
+ void collect(@NonNull WindowContainer wc) {
+ if (!wc.mTransitionController.isShellTransitionsEnabled()) return;
+ if (!expectCollecting()) return;
+ getTransition().collect(wc);
+ }
+
+ /**
+ * An interface for creating and tracking action chains.
+ */
+ static class Tracker {
+ private final ActivityTaskManagerService mAtm;
+
+ Tracker(ActivityTaskManagerService atm) {
+ mAtm = atm;
+ }
+
+ private ActionChain makeChain(String source, @LinkType int type, Transition transit) {
+ final ActionChain out = new ActionChain(source, type, transit);
+ if (!Flags.transitTrackerPlumbing()) {
+ out.mTmpAtm = mAtm;
+ }
+ return out;
+ }
+
+ private ActionChain makeChain(String source, @LinkType int type) {
+ return makeChain(source, type,
+ mAtm.getTransitionController().getCollectingTransition());
+ }
+
+ /**
+ * Starts tracking a normal action.
+ * @see #TYPE_NORMAL
+ */
+ @NonNull
+ ActionChain start(String source, Transition transit) {
+ return makeChain(source, TYPE_NORMAL, transit);
+ }
+
+ /** @see #TYPE_DEFAULT */
+ @NonNull
+ ActionChain startDefault(String source) {
+ return makeChain(source, TYPE_DEFAULT);
+ }
+
+ /**
+ * Starts tracking an action that finishes a transition.
+ * @see #TYPE_NORMAL
+ */
+ @NonNull
+ ActionChain startFinish(String source, Transition finishTransit) {
+ return makeChain(source, TYPE_FINISH, finishTransit);
+ }
+
+ /** @see #TYPE_LEGACY */
+ @NonNull
+ ActionChain startLegacy(String source) {
+ return makeChain(source, TYPE_LEGACY, null);
+ }
+
+ /** @see #TYPE_FAILSAFE */
+ @NonNull
+ ActionChain startFailsafe(String source) {
+ return makeChain(source, TYPE_FAILSAFE);
+ }
+ }
+
+ /** Helpers for usage in tests. */
+ @NonNull
+ static ActionChain test() {
+ return new ActionChain("test", TYPE_TEST, null /* transition */);
+ }
+
+ @NonNull
+ static ActionChain testFinish(Transition toFinish) {
+ return new ActionChain("test", TYPE_FINISH, toFinish);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index fb2bf39c0b7b..2ce1aa422601 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1275,10 +1275,8 @@ class ActivityMetricsLogger {
final ActivityRecord r = info.mLastLaunchedActivity;
final long lastTopLossTime = r.topResumedStateLossTime;
final WindowManagerService wm = mSupervisor.mService.mWindowManager;
- final Object controller = wm.getRecentsAnimationController();
mLoggerHandler.postDelayed(() -> {
- if (lastTopLossTime != r.topResumedStateLossTime
- || controller != wm.getRecentsAnimationController()) {
+ if (lastTopLossTime != r.topResumedStateLossTime) {
// Skip if the animation was finished in a short time.
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5ba8433146dc..21908d95c605 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -226,7 +226,6 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_F
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
-import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -234,7 +233,6 @@ import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIEN
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
import static com.android.server.wm.TaskPersister.DEBUG;
@@ -637,12 +635,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private SizeConfigurationBuckets mSizeConfigurations;
- /**
- * The precomputed display insets for resolving configuration. It will be non-null if
- * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}.
- */
- private AppCompatDisplayInsets mAppCompatDisplayInsets;
-
@VisibleForTesting
final TaskFragment.ConfigOverrideHint mResolveConfigHint;
@@ -792,22 +784,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@NonNull
final AppCompatController mAppCompatController;
- /**
- * The scale to fit at least one side of the activity to its parent. If the activity uses
- * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5.
- */
- private float mSizeCompatScale = 1f;
-
- /**
- * The bounds in global coordinates for activity in size compatibility mode.
- * @see ActivityRecord#hasSizeCompatBounds()
- */
- private Rect mSizeCompatBounds;
-
- // Whether this activity is in size compatibility mode because its bounds don't fit in parent
- // naturally.
- private boolean mInSizeCompatModeForBounds = false;
-
// Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
// requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -1257,10 +1233,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mPendingRelaunchCount != 0) {
pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount);
}
- if (mSizeCompatScale != 1f || mSizeCompatBounds != null) {
- pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds="
- + mSizeCompatBounds);
- }
if (mRemovingFromDisplay) {
pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
}
@@ -2647,7 +2619,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|| mStartingWindow == null
|| mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
// skip copy splash screen to client if it was resized
- || (mStartingData != null && mStartingData.mResizedFromTransfer)) {
+ || (mStartingData != null && mStartingData.mResizedFromTransfer)
+ || isRelaunching()) {
return false;
}
if (isTransferringSplashScreen()) {
@@ -4969,9 +4942,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
newIntents.add(intent);
}
- final boolean isSleeping() {
- final Task rootTask = getRootTask();
- return rootTask != null ? rootTask.shouldSleepActivities() : mAtmService.isSleepingLocked();
+ boolean isSleeping() {
+ return task != null ? task.shouldSleepActivities() : mAtmService.isSleepingLocked();
}
/**
@@ -4995,7 +4967,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
callerToken);
boolean unsent = true;
- final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
+ final boolean isTopActivityWhileSleeping = isSleeping() && isTopRunningActivity();
// We want to immediately deliver the intent to the activity if:
// - It is currently resumed or paused. i.e. it is currently visible to the user and we want
@@ -5570,10 +5542,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return false;
}
if (!mDisplayContent.mAppTransition.isTransitionSet()) {
- // Defer committing visibility for non-home app which is animating by recents.
- if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
- return false;
- }
+ return false;
}
if (mWaitForEnteringPinnedMode && mVisible == visible) {
// If the visibility is not changed during enter PIP, we don't want to include it in
@@ -5727,8 +5696,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private void postApplyAnimation(boolean visible, boolean fromTransition) {
final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
- | ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION);
if (!delayed && !usingShellTransitions) {
// We aren't delayed anything, but exiting windows rely on the animation finished
// callback being called in case the ActivityRecord was pretending to be delayed,
@@ -5750,7 +5718,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// animation and aren't in RESUMED state. Otherwise, we'll update client visibility in
// onAnimationFinished or activityStopped.
if (visible || (mState != RESUMED && (usingShellTransitions || !isAnimating(
- PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)))) {
+ PARENTS, ANIMATION_TYPE_APP_TRANSITION)))) {
setClientVisible(visible);
}
@@ -5862,7 +5830,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void setState(State state, String reason) {
ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s",
- this, getState(), state, reason);
+ this, mState, state, reason);
if (state == mState) {
// No need to do anything if state doesn't change.
@@ -6187,7 +6155,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Now for any activities that aren't visible to the user, make sure they no longer are
// keeping the screen frozen.
if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + getState());
+ Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + mState);
}
try {
final boolean canEnterPictureInPicture = checkEnterPictureInPictureState(
@@ -6203,7 +6171,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
setVisibility(false);
- switch (getState()) {
+ switch (mState) {
case STOPPING:
case STOPPED:
// Reset the flag indicating that an app can enter picture-in-picture once the
@@ -6439,7 +6407,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTaskSupervisor.mStoppingActivities.remove(this);
if (getDisplayArea().allResumedActivitiesComplete()) {
// Construct the compat environment at a relatively stable state if needed.
- updateAppCompatDisplayInsets();
+ mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets();
mRootWindowContainer.executeAppTransitionForAllDisplay();
}
@@ -7728,7 +7696,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Ensure that the activity content is hidden when the decor surface is boosted to
// prevent UI redressing attack.
&& !isDecorSurfaceBoosted)
- || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION
| ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
@@ -8072,7 +8040,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
!= getRequestedConfigurationOrientation(false /*forDisplay */)) {
// Do not change the requested configuration now, because this will be done when setting
// the orientation below with the new mAppCompatDisplayInsets
- clearSizeCompatModeAttributes();
+ mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatModeAttributes();
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Setting requested orientation %s for %s",
@@ -8205,18 +8173,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Nullable
AppCompatDisplayInsets getAppCompatDisplayInsets() {
- if (mAppCompatController.getTransparentPolicy().isRunning()) {
- return mAppCompatController.getTransparentPolicy().getInheritedAppCompatDisplayInsets();
- }
- return mAppCompatDisplayInsets;
- }
-
- /**
- * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without
- * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
- */
- boolean hasAppCompatDisplayInsetsWithoutInheritance() {
- return mAppCompatDisplayInsets != null;
+ return mAppCompatController.getAppCompatSizeCompatModePolicy().getAppCompatDisplayInsets();
}
/**
@@ -8224,7 +8181,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* density than its parent or its bounds don't fit in parent naturally.
*/
boolean inSizeCompatMode() {
- if (mInSizeCompatModeForBounds) {
+ final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController
+ .getAppCompatSizeCompatModePolicy();
+ if (scmPolicy.isInSizeCompatModeForBounds()) {
return true;
}
if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets()
@@ -8321,69 +8280,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
boolean hasSizeCompatBounds() {
- return mSizeCompatBounds != null;
- }
-
- // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
- private void updateAppCompatDisplayInsets() {
- if (getAppCompatDisplayInsets() != null || !shouldCreateAppCompatDisplayInsets()) {
- // The override configuration is set only once in size compatibility mode.
- return;
- }
-
- Configuration overrideConfig = getRequestedOverrideConfiguration();
- final Configuration fullConfig = getConfiguration();
-
- // Ensure the screen related fields are set. It is used to prevent activity relaunch
- // when moving between displays. For screenWidthDp and screenWidthDp, because they
- // are relative to bounds and density, they will be calculated in
- // {@link Task#computeConfigResourceOverrides} and the result will also be
- // relatively fixed.
- overrideConfig.colorMode = fullConfig.colorMode;
- overrideConfig.densityDpi = fullConfig.densityDpi;
- // The smallest screen width is the short side of screen bounds. Because the bounds
- // and density won't be changed, smallestScreenWidthDp is also fixed.
- overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
- if (ActivityInfo.isFixedOrientation(getOverrideOrientation())) {
- // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
- // apply runtime rotation changes.
- overrideConfig.windowConfiguration.setRotation(
- fullConfig.windowConfiguration.getRotation());
- }
-
- final Rect letterboxedContainerBounds = mAppCompatController
- .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
-
- // The role of AppCompatDisplayInsets is like the override bounds.
- mAppCompatDisplayInsets =
- new AppCompatDisplayInsets(
- mDisplayContent, this, letterboxedContainerBounds,
- mResolveConfigHint.mUseOverrideInsetsForConfig);
- }
-
- private void clearSizeCompatModeAttributes() {
- mInSizeCompatModeForBounds = false;
- final float lastSizeCompatScale = mSizeCompatScale;
- mSizeCompatScale = 1f;
- if (mSizeCompatScale != lastSizeCompatScale) {
- forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
- }
- mSizeCompatBounds = null;
- mAppCompatDisplayInsets = null;
- mAppCompatController.getTransparentPolicy().clearInheritedAppCompatDisplayInsets();
- }
-
- @VisibleForTesting
- void clearSizeCompatMode() {
- clearSizeCompatModeAttributes();
- // Clear config override in #updateAppCompatDisplayInsets().
- final int activityType = getActivityType();
- final Configuration overrideConfig = getRequestedOverrideConfiguration();
- overrideConfig.unset();
- // Keep the activity type which was set when attaching to a task to prevent leaving it
- // undefined.
- overrideConfig.windowConfiguration.setActivityType(activityType);
- onRequestedOverrideConfigurationChanged(overrideConfig);
+ return mAppCompatController.getAppCompatSizeCompatModePolicy().hasSizeCompatBounds();
}
@Override
@@ -8401,7 +8298,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
float getCompatScale() {
- return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
+ // We need to invoke {#getCompatScale()} only if the CompatScale is not available.
+ return mAppCompatController.getAppCompatSizeCompatModePolicy()
+ .getCompatScaleIfAvailable(ActivityRecord.super::getCompatScale);
}
@Override
@@ -8469,8 +8368,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
resolveAspectRatioRestriction(newParentConfiguration);
}
final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
if (appCompatDisplayInsets != null) {
- resolveSizeCompatModeConfiguration(newParentConfiguration, appCompatDisplayInsets);
+ scmPolicy.resolveSizeCompatModeConfiguration(newParentConfiguration,
+ appCompatDisplayInsets, mTmpBounds);
} else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
// We ignore activities' requested orientation in multi-window modes. They may be
// taken into consideration in resolveFixedOrientationConfiguration call above.
@@ -8488,13 +8390,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (!Flags.immersiveAppRepositioning()
&& !mAppCompatController.getAppCompatAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio()
- && !mInSizeCompatModeForBounds
+ && !scmPolicy.isInSizeCompatModeForBounds()
&& !mAppCompatController.getAppCompatAspectRatioOverrides()
.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
- if (isFixedOrientationLetterboxAllowed || mAppCompatDisplayInsets != null
+ if (isFixedOrientationLetterboxAllowed
+ || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
// In fullscreen, can be letterboxed for aspect ratio.
|| !inMultiWindowMode()) {
updateResolvedBoundsPosition(newParentConfiguration);
@@ -8502,7 +8405,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
boolean isIgnoreOrientationRequest = mDisplayContent != null
&& mDisplayContent.getIgnoreOrientationRequest();
- if (mAppCompatDisplayInsets == null
+ if (!scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
// for size compat mode set in updateAppCompatDisplayInsets
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
@@ -8551,7 +8454,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
!matchParentBounds(),
- mAppCompatDisplayInsets != null,
+ scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance(),
shouldCreateAppCompatDisplayInsets());
}
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
@@ -8575,7 +8478,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride);
}
- private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
+ void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@NonNull Configuration parentConfig) {
task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
// Reset the temp info which should only take effect for the specified computation.
@@ -8627,7 +8530,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mAppCompatController.getTransparentPolicy().isRunning()) {
return mAppCompatController.getTransparentPolicy().getInheritedAppCompatState();
}
- if (mInSizeCompatModeForBounds) {
+ final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController
+ .getAppCompatSizeCompatModePolicy();
+ if (scmPolicy.isInSizeCompatModeForBounds()) {
return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
}
// Letterbox for fixed orientation. This check returns true only when an activity is
@@ -8663,8 +8568,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (resolvedBounds.isEmpty()) {
return;
}
- final Rect screenResolvedBounds =
- mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
+ final Rect screenResolvedBounds = scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
final float screenResolvedBoundsWidth = screenResolvedBounds.width();
@@ -8720,14 +8626,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
- screenResolvedBounds.top + parentAppBounds.top);
}
}
-
- if (mSizeCompatBounds != null) {
- mSizeCompatBounds.offset(offsetX , offsetY);
- final int dy = mSizeCompatBounds.top - resolvedBounds.top;
- final int dx = mSizeCompatBounds.left - resolvedBounds.left;
- offsetBounds(resolvedConfig, dx, dy);
- } else {
- offsetBounds(resolvedConfig, offsetX, offsetY);
+ // If in SCM, apply offset to resolved bounds relative to size compat bounds. If
+ // not, apply directly to resolved bounds.
+ if (!scmPolicy.applyOffsetIfNeeded(resolvedBounds, resolvedConfig, offsetX, offsetY)) {
+ AppCompatUtils.offsetBounds(resolvedConfig, offsetX, offsetY);
}
// If the top is aligned with parentAppBounds add the vertical insets back so that the app
@@ -8735,9 +8637,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top
&& !isImmersiveMode) {
resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top;
- if (mSizeCompatBounds != null) {
- mSizeCompatBounds.top = parentBounds.top;
- }
+ scmPolicy.alignToTopIfNeeded(parentBounds);
}
// Since bounds has changed, the configuration needs to be computed accordingly.
@@ -8747,13 +8647,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// easier to resolve the relative position in parent container. However, if the activity is
// scaled, the position should follow the scale because the configuration will be sent to
// the client which is expected to be in a scaled environment.
- if (mSizeCompatScale != 1f) {
- final int screenPosX = resolvedBounds.left;
- final int screenPosY = resolvedBounds.top;
- final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX;
- final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY;
- offsetBounds(resolvedConfig, dx, dy);
- }
+ scmPolicy.applySizeCompatScaleIfNeeded(resolvedBounds, resolvedConfig);
}
boolean isImmersiveMode(@NonNull Rect parentBounds) {
@@ -8775,7 +8669,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@NonNull Rect getScreenResolvedBounds() {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
- return mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
+ return scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
}
void recomputeConfiguration() {
@@ -8927,8 +8823,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return;
}
final AppCompatDisplayInsets mAppCompatDisplayInsets = getAppCompatDisplayInsets();
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
- if (mAppCompatDisplayInsets != null
+ if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
&& !mAppCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
@@ -8981,7 +8879,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
.applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds,
containingBoundsWithInsets, containingBounds);
- if (mAppCompatDisplayInsets != null) {
+ if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) {
mAppCompatDisplayInsets.getBoundsByRotation(mTmpBounds,
newParentConfig.windowConfiguration.getRotation());
if (resolvedBounds.width() != mTmpBounds.width()
@@ -9042,246 +8940,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- /**
- * Resolves consistent screen configuration for orientation and rotation changes without
- * inheriting the parent bounds.
- */
- private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration,
- @NonNull AppCompatDisplayInsets appCompatDisplayInsets) {
- final Configuration resolvedConfig = getResolvedOverrideConfiguration();
- final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
-
- // When an activity needs to be letterboxed because of fixed orientation, use fixed
- // orientation bounds (stored in resolved bounds) instead of parent bounds since the
- // activity will be displayed within them even if it is in size compat mode. They should be
- // saved here before resolved bounds are overridden below.
- final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
- ? mAppCompatController.getAppCompatAspectRatioPolicy()
- .isAspectRatioApplied()
- : mAppCompatController.getAppCompatAspectRatioPolicy()
- .isLetterboxedForFixedOrientationAndAspectRatio();
- final Rect containerBounds = useResolvedBounds
- ? new Rect(resolvedBounds)
- : newParentConfiguration.windowConfiguration.getBounds();
- final Rect containerAppBounds = useResolvedBounds
- ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
- : mResolveConfigHint.mParentAppBoundsOverride;
-
- final int requestedOrientation = getRequestedConfigurationOrientation();
- final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
- final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig
- ? mResolveConfigHint.mTmpOverrideConfigOrientation
- : newParentConfiguration.orientation;
- final int orientation = orientationRequested
- ? requestedOrientation
- // We should use the original orientation of the activity when possible to avoid
- // forcing the activity in the opposite orientation.
- : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
- ? appCompatDisplayInsets.mOriginalRequestedOrientation
- : parentOrientation;
- int rotation = newParentConfiguration.windowConfiguration.getRotation();
- final boolean isFixedToUserRotation = mDisplayContent == null
- || mDisplayContent.getDisplayRotation().isFixedToUserRotation();
- if (!isFixedToUserRotation && !appCompatDisplayInsets.mIsFloating) {
- // Use parent rotation because the original display can be rotated.
- resolvedConfig.windowConfiguration.setRotation(rotation);
- } else {
- final int overrideRotation = resolvedConfig.windowConfiguration.getRotation();
- if (overrideRotation != ROTATION_UNDEFINED) {
- rotation = overrideRotation;
- }
- }
-
- // Use compat insets to lock width and height. We should not use the parent width and height
- // because apps in compat mode should have a constant width and height. The compat insets
- // are locked when the app is first launched and are never changed after that, so we can
- // rely on them to contain the original and unchanging width and height of the app.
- final Rect containingAppBounds = new Rect();
- final Rect containingBounds = mTmpBounds;
- appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
- orientation, orientationRequested, isFixedToUserRotation);
- resolvedBounds.set(containingBounds);
- // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
- if (!appCompatDisplayInsets.mIsFloating) {
- mAppCompatController.getAppCompatAspectRatioPolicy()
- .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
- containingBounds);
- }
-
- // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
- // are calculated in compat container space. The actual position on screen will be applied
- // later, so the calculation is simpler that doesn't need to involve offset from parent.
- mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets;
- computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
- // Use current screen layout as source because the size of app is independent to parent.
- resolvedConfig.screenLayout = computeScreenLayout(
- getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
- resolvedConfig.screenHeightDp);
-
- // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
- // the parent bounds appropriately.
- if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
- resolvedConfig.orientation = parentOrientation;
- }
-
- // Below figure is an example that puts an activity which was launched in a larger container
- // into a smaller container.
- // The outermost rectangle is the real display bounds.
- // "@" is the container app bounds (parent bounds or fixed orientation bounds)
- // "#" is the {@code resolvedBounds} that applies to application.
- // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
- // ------------------------------
- // | |
- // | @@@@*********@@@@### |
- // | @ * * @ # |
- // | @ * * @ # |
- // | @ * * @ # |
- // | @@@@*********@@@@ # |
- // ---------#--------------#-----
- // # #
- // ################
- // The application is still layouted in "#" since it was launched, and it will be visually
- // scaled and positioned to "*".
-
- final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
-
- // Calculates the scale the size compatibility bounds into the region which is available
- // to application.
- final float lastSizeCompatScale = mSizeCompatScale;
- updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
-
- final int containerTopInset = containerAppBounds.top - containerBounds.top;
- final boolean topNotAligned =
- containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
- if (mSizeCompatScale != 1f || topNotAligned) {
- if (mSizeCompatBounds == null) {
- mSizeCompatBounds = new Rect();
- }
- mSizeCompatBounds.set(resolvedAppBounds);
- mSizeCompatBounds.offsetTo(0, 0);
- mSizeCompatBounds.scale(mSizeCompatScale);
- // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
- mSizeCompatBounds.bottom += containerTopInset;
- } else {
- mSizeCompatBounds = null;
- }
- if (mSizeCompatScale != lastSizeCompatScale) {
- forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
- }
-
- // The position will be later adjusted in updateResolvedBoundsPosition.
- // Above coordinates are in "@" space, now place "*" and "#" to screen space.
- final boolean fillContainer = resolvedBounds.equals(containingBounds);
- final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
- final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
-
- if (screenPosX != 0 || screenPosY != 0) {
- if (mSizeCompatBounds != null) {
- mSizeCompatBounds.offset(screenPosX, screenPosY);
- }
- // Add the global coordinates and remove the local coordinates.
- final int dx = screenPosX - resolvedBounds.left;
- final int dy = screenPosY - resolvedBounds.top;
- offsetBounds(resolvedConfig, dx, dy);
- }
-
- mInSizeCompatModeForBounds =
- isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds);
- }
-
- void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
- mSizeCompatScale = mAppCompatController.getTransparentPolicy()
- .findOpaqueNotFinishingActivityBelow()
- .map(activityRecord -> activityRecord.mSizeCompatScale)
- .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
- }
-
- private float calculateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
- final int contentW = resolvedAppBounds.width();
- final int contentH = resolvedAppBounds.height();
- final int viewportW = containerAppBounds.width();
- final int viewportH = containerAppBounds.height();
- // Allow an application to be up-scaled if its window is smaller than its
- // original container or if it's a freeform window in desktop mode.
- boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
- || (canEnterDesktopMode(mAtmService.mContext)
- && getWindowingMode() == WINDOWING_MODE_FREEFORM);
- return shouldAllowUpscaling ? Math.min(
- (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
- }
-
- private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
- if (mAppCompatController.getTransparentPolicy().isRunning()) {
- // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
- // is letterboxed.
- return false;
- }
- final int appWidth = appBounds.width();
- final int appHeight = appBounds.height();
- final int containerAppWidth = containerBounds.width();
- final int containerAppHeight = containerBounds.height();
-
- if (containerAppWidth == appWidth && containerAppHeight == appHeight) {
- // Matched the container bounds.
- return false;
- }
- if (containerAppWidth > appWidth && containerAppHeight > appHeight) {
- // Both sides are smaller than the container.
- return true;
- }
- if (containerAppWidth < appWidth || containerAppHeight < appHeight) {
- // One side is larger than the container.
- return true;
- }
-
- // The rest of the condition is that only one side is smaller than the container, but it
- // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
- final float maxAspectRatio = getMaxAspectRatio();
- if (maxAspectRatio > 0) {
- final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
- / Math.min(appWidth, appHeight);
- if (aspectRatio >= maxAspectRatio) {
- // The current size has reached the max aspect ratio.
- return false;
- }
- }
- final float minAspectRatio = getMinAspectRatio();
- if (minAspectRatio > 0) {
- // The activity should have at least the min aspect ratio, so this checks if the
- // container still has available space to provide larger aspect ratio.
- final float containerAspectRatio =
- (0.5f + Math.max(containerAppWidth, containerAppHeight))
- / Math.min(containerAppWidth, containerAppHeight);
- if (containerAspectRatio <= minAspectRatio) {
- // The long side has reached the parent.
- return false;
- }
- }
- return true;
- }
-
- /** @return The horizontal / vertical offset of putting the content in the center of viewport.*/
- private static int getCenterOffset(int viewportDim, int contentDim) {
- return (int) ((viewportDim - contentDim + 1) * 0.5f);
- }
-
- private static void offsetBounds(Configuration inOutConfig, int offsetX, int offsetY) {
- inOutConfig.windowConfiguration.getBounds().offset(offsetX, offsetY);
- inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY);
- }
-
@Override
public Rect getBounds() {
// TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
final Rect superBounds = super.getBounds();
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
return mAppCompatController.getTransparentPolicy().findOpaqueNotFinishingActivityBelow()
.map(ActivityRecord::getBounds)
- .orElseGet(() -> {
- if (mSizeCompatBounds != null) {
- return mSizeCompatBounds;
- }
- return superBounds;
- });
+ .orElseGet(() -> scmPolicy.getAppSizeCompatBoundsIfAvailable(superBounds));
}
@Override
@@ -9612,7 +9279,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mVisibleRequested) {
// Calling from here rather than resolveOverrideConfiguration to ensure that this is
// called after full config is updated in ConfigurationContainer#onConfigurationChanged.
- updateAppCompatDisplayInsets();
+ mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets();
}
// Short circuit: if the two full configurations are equal (the common case), then there is
@@ -9952,7 +9619,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Reset the existing override configuration so it can be updated according to the latest
// configuration.
- clearSizeCompatMode();
+ mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
if (!attachedToProcess()) {
return;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2f74a9d5816b..03c7fd1e3ffe 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -438,13 +438,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/** It is set from keyguard-going-away to set-keyguard-shown. */
static final int DEMOTE_TOP_REASON_DURING_UNLOCKING = 1;
- /** It is set if legacy recents animation is running. */
- static final int DEMOTE_TOP_REASON_ANIMATING_RECENTS = 1 << 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DEMOTE_TOP_REASON_DURING_UNLOCKING,
- DEMOTE_TOP_REASON_ANIMATING_RECENTS,
})
@interface DemoteTopReason {}
@@ -798,6 +795,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
WindowOrganizerController mWindowOrganizerController;
TaskOrganizerController mTaskOrganizerController;
TaskFragmentOrganizerController mTaskFragmentOrganizerController;
+ ActionChain.Tracker mChainTracker;
@Nullable
private BackgroundActivityStartCallback mBackgroundActivityStartCallback;
@@ -872,6 +870,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mInternal = new LocalService();
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED);
mWindowOrganizerController = new WindowOrganizerController(this);
+ mChainTracker = new ActionChain.Tracker(this);
mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController;
mTaskFragmentOrganizerController =
mWindowOrganizerController.mTaskFragmentOrganizerController;
@@ -1777,19 +1776,15 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public void preloadRecentsActivity(Intent intent) {
enforceTaskPermission("preloadRecentsActivity()");
- final int callingPid = Binder.getCallingPid();
- final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
final String recentsFeatureId = mRecentTasks.getRecentsComponentFeatureId();
final int recentsUid = mRecentTasks.getRecentsComponentUid();
- final WindowProcessController caller = getProcessController(callingPid, callingUid);
-
final RecentsAnimation anim = new RecentsAnimation(this, mTaskSupervisor,
- getActivityStartController(), mWindowManager, intent, recentsComponent,
- recentsFeatureId, recentsUid, caller);
+ getActivityStartController(), intent, recentsComponent,
+ recentsFeatureId, recentsUid);
anim.preloadRecentsActivity();
}
} finally {
@@ -3624,6 +3619,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
+ boolean isPowerModePreApplied = false;
+ if (mPowerModeReasons == 0) {
+ startPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+ isPowerModePreApplied = true;
+ }
// Keyguard asked us to clear the home task snapshot before going away, so do that.
if ((flags & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) {
mActivityClientController.invalidateHomeTaskSnapshot(null /* token */);
@@ -3632,9 +3632,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mDemoteTopAppReasons |= DEMOTE_TOP_REASON_DURING_UNLOCKING;
}
- mRootWindowContainer.forAllDisplays(displayContent -> {
- mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags);
- });
+ boolean foundResumed = false;
+ for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+ final DisplayContent dc = mRootWindowContainer.getChildAt(i);
+ final boolean wasNoResumed = dc.mFocusedApp == null
+ || !dc.mFocusedApp.isState(RESUMED);
+ mKeyguardController.keyguardGoingAway(dc.mDisplayId, flags);
+ if (wasNoResumed && dc.mFocusedApp != null && dc.mFocusedApp.isState(RESUMED)) {
+ foundResumed = true;
+ }
+ }
+ if (isPowerModePreApplied && !foundResumed) {
+ endPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+ }
}
WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
if (wallpaperManagerInternal != null) {
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 3c3b77374d70..173362c16728 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -46,6 +46,8 @@ class AppCompatController {
private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
@NonNull
private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
+ @NonNull
+ private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -67,6 +69,8 @@ class AppCompatController {
wmService.mAppCompatConfiguration);
mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
+ mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
+ mAppCompatOverrides);
}
@NonNull
@@ -152,9 +156,15 @@ class AppCompatController {
return mAppCompatOverrides.getAppCompatLetterboxOverrides();
}
+ @NonNull
+ AppCompatSizeCompatModePolicy getAppCompatSizeCompatModePolicy() {
+ return mAppCompatSizeCompatModePolicy;
+ }
+
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
getTransparentPolicy().dump(pw, prefix);
getAppCompatLetterboxPolicy().dump(pw, prefix);
+ getAppCompatSizeCompatModePolicy().dump(pw, prefix);
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
new file mode 100644
index 000000000000..3be266e2951b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+
+import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.window.flags.Flags;
+
+import java.io.PrintWriter;
+import java.util.function.DoubleSupplier;
+
+/**
+ * Encapsulate logic related to the SizeCompatMode.
+ */
+class AppCompatSizeCompatModePolicy {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final AppCompatOverrides mAppCompatOverrides;
+
+ // Whether this activity is in size compatibility mode because its bounds don't fit in parent
+ // naturally.
+ private boolean mInSizeCompatModeForBounds = false;
+ /**
+ * The scale to fit at least one side of the activity to its parent. If the activity uses
+ * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5.
+ */
+ private float mSizeCompatScale = 1f;
+
+ /**
+ * The bounds in global coordinates for activity in size compatibility mode.
+ * @see #hasSizeCompatBounds()
+ */
+ private Rect mSizeCompatBounds;
+
+ /**
+ * The precomputed display insets for resolving configuration. It will be non-null if
+ * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}.
+ */
+ @Nullable
+ private AppCompatDisplayInsets mAppCompatDisplayInsets;
+
+ AppCompatSizeCompatModePolicy(@NonNull ActivityRecord activityRecord,
+ @NonNull AppCompatOverrides appCompatOverrides) {
+ mActivityRecord = activityRecord;
+ mAppCompatOverrides = appCompatOverrides;
+ }
+
+ boolean isInSizeCompatModeForBounds() {
+ return mInSizeCompatModeForBounds;
+ }
+
+ void setInSizeCompatModeForBounds(boolean inSizeCompatModeForBounds) {
+ mInSizeCompatModeForBounds = inSizeCompatModeForBounds;
+ }
+
+ boolean hasSizeCompatBounds() {
+ return mSizeCompatBounds != null;
+ }
+
+ /**
+ * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without
+ * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
+ */
+ boolean hasAppCompatDisplayInsetsWithoutInheritance() {
+ return mAppCompatDisplayInsets != null;
+ }
+
+ @Nullable
+ AppCompatDisplayInsets getAppCompatDisplayInsets() {
+ final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController
+ .getTransparentPolicy();
+ if (transparentPolicy.isRunning()) {
+ return transparentPolicy.getInheritedAppCompatDisplayInsets();
+ }
+ return mAppCompatDisplayInsets;
+ }
+
+ float getCompatScaleIfAvailable(@NonNull DoubleSupplier scaleWhenNotAvailable) {
+ return hasSizeCompatBounds() ? mSizeCompatScale
+ : (float) scaleWhenNotAvailable.getAsDouble();
+ }
+
+ @NonNull
+ Rect getAppSizeCompatBoundsIfAvailable(@NonNull Rect boundsWhenNotAvailable) {
+ return hasSizeCompatBounds() ? mSizeCompatBounds : boundsWhenNotAvailable;
+ }
+
+ @NonNull
+ Rect replaceResolvedBoundsIfNeeded(@NonNull Rect resolvedBounds) {
+ return hasSizeCompatBounds() ? mSizeCompatBounds : resolvedBounds;
+ }
+
+ boolean applyOffsetIfNeeded(@NonNull Rect resolvedBounds,
+ @NonNull Configuration resolvedConfig, int offsetX, int offsetY) {
+ if (hasSizeCompatBounds()) {
+ mSizeCompatBounds.offset(offsetX , offsetY);
+ final int dy = mSizeCompatBounds.top - resolvedBounds.top;
+ final int dx = mSizeCompatBounds.left - resolvedBounds.left;
+ AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+ return true;
+ }
+ return false;
+ }
+
+ void alignToTopIfNeeded(@NonNull Rect parentBounds) {
+ if (hasSizeCompatBounds()) {
+ mSizeCompatBounds.top = parentBounds.top;
+ }
+ }
+
+ void applySizeCompatScaleIfNeeded(@NonNull Rect resolvedBounds,
+ @NonNull Configuration resolvedConfig) {
+ if (mSizeCompatScale != 1f) {
+ final int screenPosX = resolvedBounds.left;
+ final int screenPosY = resolvedBounds.top;
+ final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX;
+ final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY;
+ AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+ }
+ }
+
+ void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds) {
+ mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
+ .findOpaqueNotFinishingActivityBelow()
+ .map(activityRecord -> mSizeCompatScale)
+ .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
+ }
+
+ void clearSizeCompatModeAttributes() {
+ mInSizeCompatModeForBounds = false;
+ final float lastSizeCompatScale = mSizeCompatScale;
+ mSizeCompatScale = 1f;
+ if (mSizeCompatScale != lastSizeCompatScale) {
+ mActivityRecord.forAllWindows(WindowState::updateGlobalScale,
+ false /* traverseTopToBottom */);
+ }
+ mSizeCompatBounds = null;
+ mAppCompatDisplayInsets = null;
+ mActivityRecord.mAppCompatController.getTransparentPolicy()
+ .clearInheritedAppCompatDisplayInsets();
+ }
+
+ void clearSizeCompatMode() {
+ clearSizeCompatModeAttributes();
+ // Clear config override in #updateAppCompatDisplayInsets().
+ final int activityType = mActivityRecord.getActivityType();
+ final Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration();
+ overrideConfig.unset();
+ // Keep the activity type which was set when attaching to a task to prevent leaving it
+ // undefined.
+ overrideConfig.windowConfiguration.setActivityType(activityType);
+ mActivityRecord.onRequestedOverrideConfigurationChanged(overrideConfig);
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ if (mSizeCompatScale != 1f || hasSizeCompatBounds()) {
+ pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds="
+ + mSizeCompatBounds);
+ }
+ }
+
+ /**
+ * Resolves consistent screen configuration for orientation and rotation changes without
+ * inheriting the parent bounds.
+ */
+ void resolveSizeCompatModeConfiguration(@NonNull Configuration newParentConfiguration,
+ @NonNull AppCompatDisplayInsets appCompatDisplayInsets, @NonNull Rect tmpBounds) {
+ final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
+ final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+
+ // When an activity needs to be letterboxed because of fixed orientation, use fixed
+ // orientation bounds (stored in resolved bounds) instead of parent bounds since the
+ // activity will be displayed within them even if it is in size compat mode. They should be
+ // saved here before resolved bounds are overridden below.
+ final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
+ .getAppCompatAspectRatioPolicy();
+ final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
+ ? aspectRatioPolicy.isAspectRatioApplied()
+ : aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio();
+ final Rect containerBounds = useResolvedBounds
+ ? new Rect(resolvedBounds)
+ : newParentConfiguration.windowConfiguration.getBounds();
+ final Rect containerAppBounds = useResolvedBounds
+ ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
+ : mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
+
+ final int requestedOrientation = mActivityRecord.getRequestedConfigurationOrientation();
+ final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
+ final int parentOrientation = mActivityRecord.mResolveConfigHint.mUseOverrideInsetsForConfig
+ ? mActivityRecord.mResolveConfigHint.mTmpOverrideConfigOrientation
+ : newParentConfiguration.orientation;
+ final int orientation = orientationRequested
+ ? requestedOrientation
+ // We should use the original orientation of the activity when possible to avoid
+ // forcing the activity in the opposite orientation.
+ : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+ ? appCompatDisplayInsets.mOriginalRequestedOrientation
+ : parentOrientation;
+ int rotation = newParentConfiguration.windowConfiguration.getRotation();
+ final boolean isFixedToUserRotation = mActivityRecord.mDisplayContent == null
+ || mActivityRecord.mDisplayContent.getDisplayRotation().isFixedToUserRotation();
+ if (!isFixedToUserRotation && !appCompatDisplayInsets.mIsFloating) {
+ // Use parent rotation because the original display can be rotated.
+ resolvedConfig.windowConfiguration.setRotation(rotation);
+ } else {
+ final int overrideRotation = resolvedConfig.windowConfiguration.getRotation();
+ if (overrideRotation != ROTATION_UNDEFINED) {
+ rotation = overrideRotation;
+ }
+ }
+
+ // Use compat insets to lock width and height. We should not use the parent width and height
+ // because apps in compat mode should have a constant width and height. The compat insets
+ // are locked when the app is first launched and are never changed after that, so we can
+ // rely on them to contain the original and unchanging width and height of the app.
+ final Rect containingAppBounds = new Rect();
+ final Rect containingBounds = tmpBounds;
+ appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
+ orientation, orientationRequested, isFixedToUserRotation);
+ resolvedBounds.set(containingBounds);
+ // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
+ if (!appCompatDisplayInsets.mIsFloating) {
+ mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
+ containingBounds);
+ }
+
+ // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
+ // are calculated in compat container space. The actual position on screen will be applied
+ // later, so the calculation is simpler that doesn't need to involve offset from parent.
+ mActivityRecord.mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets;
+ mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+ // Use current screen layout as source because the size of app is independent to parent.
+ resolvedConfig.screenLayout = ActivityRecord.computeScreenLayout(
+ mActivityRecord.getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
+ resolvedConfig.screenHeightDp);
+
+ // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
+ // the parent bounds appropriately.
+ if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
+ resolvedConfig.orientation = parentOrientation;
+ }
+
+ // Below figure is an example that puts an activity which was launched in a larger container
+ // into a smaller container.
+ // The outermost rectangle is the real display bounds.
+ // "@" is the container app bounds (parent bounds or fixed orientation bounds)
+ // "#" is the {@code resolvedBounds} that applies to application.
+ // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
+ // ------------------------------
+ // | |
+ // | @@@@*********@@@@### |
+ // | @ * * @ # |
+ // | @ * * @ # |
+ // | @ * * @ # |
+ // | @@@@*********@@@@ # |
+ // ---------#--------------#-----
+ // # #
+ // ################
+ // The application is still layouted in "#" since it was launched, and it will be visually
+ // scaled and positioned to "*".
+
+ final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+ // Calculates the scale the size compatibility bounds into the region which is available
+ // to application.
+ final float lastSizeCompatScale = mSizeCompatScale;
+ updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
+
+ final int containerTopInset = containerAppBounds.top - containerBounds.top;
+ final boolean topNotAligned =
+ containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
+ if (mSizeCompatScale != 1f || topNotAligned) {
+ if (mSizeCompatBounds == null) {
+ mSizeCompatBounds = new Rect();
+ }
+ mSizeCompatBounds.set(resolvedAppBounds);
+ mSizeCompatBounds.offsetTo(0, 0);
+ mSizeCompatBounds.scale(mSizeCompatScale);
+ // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
+ mSizeCompatBounds.bottom += containerTopInset;
+ } else {
+ mSizeCompatBounds = null;
+ }
+ if (mSizeCompatScale != lastSizeCompatScale) {
+ mActivityRecord.forAllWindows(WindowState::updateGlobalScale,
+ false /* traverseTopToBottom */);
+ }
+
+ // The position will be later adjusted in updateResolvedBoundsPosition.
+ // Above coordinates are in "@" space, now place "*" and "#" to screen space.
+ final boolean fillContainer = resolvedBounds.equals(containingBounds);
+ final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
+ final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
+
+ if (screenPosX != 0 || screenPosY != 0) {
+ if (hasSizeCompatBounds()) {
+ mSizeCompatBounds.offset(screenPosX, screenPosY);
+ }
+ // Add the global coordinates and remove the local coordinates.
+ final int dx = screenPosX - resolvedBounds.left;
+ final int dy = screenPosY - resolvedBounds.top;
+ AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+ }
+
+ mInSizeCompatModeForBounds = isInSizeCompatModeForBounds(resolvedAppBounds,
+ containerAppBounds);
+ }
+
+ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+ void updateAppCompatDisplayInsets() {
+ if (getAppCompatDisplayInsets() != null
+ || !mActivityRecord.shouldCreateAppCompatDisplayInsets()) {
+ // The override configuration is set only once in size compatibility mode.
+ return;
+ }
+
+ Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration();
+ final Configuration fullConfig = mActivityRecord.getConfiguration();
+
+ // Ensure the screen related fields are set. It is used to prevent activity relaunch
+ // when moving between displays. For screenWidthDp and screenWidthDp, because they
+ // are relative to bounds and density, they will be calculated in
+ // {@link Task#computeConfigResourceOverrides} and the result will also be
+ // relatively fixed.
+ overrideConfig.colorMode = fullConfig.colorMode;
+ overrideConfig.densityDpi = fullConfig.densityDpi;
+ // The smallest screen width is the short side of screen bounds. Because the bounds
+ // and density won't be changed, smallestScreenWidthDp is also fixed.
+ overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
+ if (ActivityInfo.isFixedOrientation(mActivityRecord.getOverrideOrientation())) {
+ // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
+ // apply runtime rotation changes.
+ overrideConfig.windowConfiguration.setRotation(
+ fullConfig.windowConfiguration.getRotation());
+ }
+
+ final Rect letterboxedContainerBounds = mActivityRecord.mAppCompatController
+ .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
+
+ // The role of AppCompatDisplayInsets is like the override bounds.
+ mAppCompatDisplayInsets =
+ new AppCompatDisplayInsets(mActivityRecord.mDisplayContent, mActivityRecord,
+ letterboxedContainerBounds, mActivityRecord.mResolveConfigHint
+ .mUseOverrideInsetsForConfig);
+ }
+
+
+ private boolean isInSizeCompatModeForBounds(final @NonNull Rect appBounds,
+ final @NonNull Rect containerBounds) {
+ if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()) {
+ // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
+ // is letterboxed.
+ return false;
+ }
+ final int appWidth = appBounds.width();
+ final int appHeight = appBounds.height();
+ final int containerAppWidth = containerBounds.width();
+ final int containerAppHeight = containerBounds.height();
+
+ if (containerAppWidth == appWidth && containerAppHeight == appHeight) {
+ // Matched the container bounds.
+ return false;
+ }
+ if (containerAppWidth > appWidth && containerAppHeight > appHeight) {
+ // Both sides are smaller than the container.
+ return true;
+ }
+ if (containerAppWidth < appWidth || containerAppHeight < appHeight) {
+ // One side is larger than the container.
+ return true;
+ }
+
+ // The rest of the condition is that only one side is smaller than the container, but it
+ // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
+ final float maxAspectRatio = mActivityRecord.getMaxAspectRatio();
+ if (maxAspectRatio > 0) {
+ final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
+ / Math.min(appWidth, appHeight);
+ if (aspectRatio >= maxAspectRatio) {
+ // The current size has reached the max aspect ratio.
+ return false;
+ }
+ }
+ final float minAspectRatio = mActivityRecord.getMinAspectRatio();
+ if (minAspectRatio > 0) {
+ // The activity should have at least the min aspect ratio, so this checks if the
+ // container still has available space to provide larger aspect ratio.
+ final float containerAspectRatio =
+ (0.5f + Math.max(containerAppWidth, containerAppHeight))
+ / Math.min(containerAppWidth, containerAppHeight);
+ if (containerAspectRatio <= minAspectRatio) {
+ // The long side has reached the parent.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds,
+ @NonNull Rect containerAppBounds) {
+ final int contentW = resolvedAppBounds.width();
+ final int contentH = resolvedAppBounds.height();
+ final int viewportW = containerAppBounds.width();
+ final int viewportH = containerAppBounds.height();
+ // Allow an application to be up-scaled if its window is smaller than its
+ // original container or if it's a freeform window in desktop mode.
+ boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
+ || (canEnterDesktopMode(mActivityRecord.mAtmService.mContext)
+ && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FREEFORM);
+ return shouldAllowUpscaling ? Math.min(
+ (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index e3ff85171c0e..69421d0d5c96 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -251,6 +251,11 @@ final class AppCompatUtils {
}
}
+ static void offsetBounds(@NonNull Configuration inOutConfig, int offsetX, int offsetY) {
+ inOutConfig.windowConfiguration.getBounds().offset(offsetX, offsetY);
+ inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY);
+ }
+
private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) {
info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index bc7e84ae9f86..90d33fbef748 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -134,8 +134,8 @@ import android.view.animation.TranslateAnimation;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.util.DumpUtils.Dump;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
@@ -402,8 +402,7 @@ public class AppTransition implements Dump {
mRemoteAnimationController.goodToGo(transit);
} else if ((isTaskOpenTransitOld(transit) || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
&& topOpeningAnim != null) {
- if (mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
- && mService.getRecentsAnimationController() == null) {
+ if (mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()) {
final NavBarFadeAnimationController controller =
new NavBarFadeAnimationController(mDisplayContent);
// For remote animation case, the nav bar fades out and in is controlled by the
@@ -471,11 +470,9 @@ public class AppTransition implements Dump {
}
private boolean needsBoosting() {
- final boolean recentsAnimRunning = mService.getRecentsAnimationController() != null;
return !mNextAppTransitionRequests.isEmpty()
|| mAppTransitionState == APP_STATE_READY
- || mAppTransitionState == APP_STATE_RUNNING
- || recentsAnimRunning;
+ || mAppTransitionState == APP_STATE_RUNNING;
}
void registerListenerLocked(AppTransitionListener listener) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 5a0cbf3e5f83..197bd5a308cd 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -68,7 +68,6 @@ import static com.android.server.wm.AppTransition.isNormalTransit;
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -173,41 +172,6 @@ public class AppTransitionController {
|| !transitionGoodToGoForTaskFragments()) {
return;
}
- final boolean isRecentsInOpening = mDisplayContent.mOpeningApps.stream().anyMatch(
- ConfigurationContainer::isActivityTypeRecents);
- // In order to avoid visual clutter caused by a conflict between app transition
- // animation and recents animation, app transition is delayed until recents finishes.
- // One exceptional case. When 3P launcher is used and a user taps a task screenshot in
- // task switcher (isRecentsInOpening=true), app transition must start even though
- // recents is running. Otherwise app transition is blocked until timeout (b/232984498).
- // When 1P launcher is used, this animation is controlled by the launcher outside of
- // the app transition, so delaying app transition doesn't cause visible delay. After
- // recents finishes, app transition is handled just to commit visibility on apps.
- if (!isRecentsInOpening) {
- final ArraySet<WindowContainer> participants = new ArraySet<>();
- participants.addAll(mDisplayContent.mOpeningApps);
- participants.addAll(mDisplayContent.mChangingContainers);
- boolean deferForRecents = false;
- for (int i = 0; i < participants.size(); i++) {
- WindowContainer wc = participants.valueAt(i);
- final ActivityRecord activity = getAppFromContainer(wc);
- if (activity == null) {
- continue;
- }
- // Don't defer recents animation if one of activity isn't running for it, that one
- // might be started from quickstep.
- if (!activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
- deferForRecents = false;
- break;
- }
- deferForRecents = true;
- }
- if (deferForRecents) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Delaying app transition for recents animation to finish");
- return;
- }
- }
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
@@ -1032,12 +996,8 @@ public class AppTransitionController {
private void applyAnimations(ArraySet<ActivityRecord> openingApps,
ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
LayoutParams animLp, boolean voiceInteraction) {
- final RecentsAnimationController rac = mService.getRecentsAnimationController();
if (transit == WindowManager.TRANSIT_OLD_UNSET
|| (openingApps.isEmpty() && closingApps.isEmpty())) {
- if (rac != null) {
- rac.sendTasksAppeared();
- }
return;
}
@@ -1075,9 +1035,6 @@ public class AppTransitionController {
voiceInteraction);
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
voiceInteraction);
- if (rac != null) {
- rac.sendTasksAppeared();
- }
for (int i = 0; i < openingApps.size(); ++i) {
openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index eb85c1a73212..f0a6e9ec1d4f 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -174,11 +174,6 @@ class AsyncRotationController extends FadeAnimationController implements Consume
mNavBarToken = w.mToken;
// Do not animate movable navigation bar (e.g. 3-buttons mode).
if (navigationBarCanMove) return;
- // Or when the navigation bar is currently controlled by recents animation.
- final RecentsAnimationController recents = mService.getRecentsAnimationController();
- if (recents != null && recents.isNavigationBarAttachedToApp()) {
- return;
- }
} else if (navigationBarCanMove || mTransitionOp == OP_CHANGE_MAY_SEAMLESS
|| mDisplayContent.mTransitionController.mNavigationBarAttachedToApp) {
action = Operation.ACTION_SEAMLESS;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 87867f6ab7d2..2cbd7f22fcba 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1820,8 +1820,10 @@ class BackNavigationController {
mNavigationMonitor.cancelBackNavigating("cancelAnimation");
mBackAnimationAdapter.getRunner().onAnimationCancelled();
} else {
- mBackAnimationAdapter.getRunner().onAnimationStart(
- targets, null, null, callback);
+ mBackAnimationAdapter.getRunner().onAnimationStart(targets,
+ mOpenAnimAdaptor.mPreparedOpenTransition != null
+ ? mOpenAnimAdaptor.mPreparedOpenTransition.getToken()
+ : null, callback);
}
} catch (RemoteException e) {
e.printStackTrace();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9bf255529768..0597ed7a1c41 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1936,7 +1936,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return false;
}
if (mLastWallpaperVisible && r.windowsCanBeWallpaperTarget()
- && mFixedRotationTransitionListener.mAnimatingRecents == null
&& !mTransitionController.isTransientLaunch(r)) {
// Use normal rotation animation for orientation change of visible wallpaper if recents
// animation is not running (it may be swiping to home).
@@ -1962,9 +1961,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Returns {@code true} if the top activity is transformed with the new rotation of display. */
boolean hasTopFixedRotationLaunchingApp() {
- return mFixedRotationLaunchingApp != null
- // Ignore animating recents because it hasn't really become the top.
- && mFixedRotationLaunchingApp != mFixedRotationTransitionListener.mAnimatingRecents;
+ return mFixedRotationLaunchingApp != null;
}
/** It usually means whether the recents activity is launching with a different rotation. */
@@ -1991,8 +1988,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
// Delay the hide animation to avoid blinking by clicking navigation bar that may
// toggle fixed rotation in a short time.
- final boolean shouldDebounce = r == mFixedRotationTransitionListener.mAnimatingRecents
- || mTransitionController.isTransientLaunch(r);
+ final boolean shouldDebounce = mTransitionController.isTransientLaunch(r);
startAsyncRotation(shouldDebounce);
} else if (mFixedRotationLaunchingApp != null && r == null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
@@ -2024,12 +2020,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// the heavy operations. This also benefits that the states of multiple activities
// are handled together.
r.linkFixedRotationTransform(prevRotatedLaunchingApp);
- if (r != mFixedRotationTransitionListener.mAnimatingRecents) {
- // Only update the record for normal activity so the display orientation can be
- // updated when the transition is done if it becomes the top. And the case of
- // recents can be handled when the recents animation is finished.
- setFixedRotationLaunchingAppUnchecked(r, rotation);
- }
+ // Only update the record for normal activity so the display orientation can be
+ // updated when the transition is done if it becomes the top.
+ setFixedRotationLaunchingAppUnchecked(r, rotation);
return;
}
@@ -5899,18 +5892,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final Region local = Region.obtain();
final int[] remainingLeftRight =
{mSystemGestureExclusionLimit, mSystemGestureExclusionLimit};
- final RecentsAnimationController recentsAnimationController =
- mWmService.getRecentsAnimationController();
// Traverse all windows top down to assemble the gesture exclusion rects.
// For each window, we only take the rects that fall within its touchable region.
forAllWindows(w -> {
- final boolean ignoreRecentsAnimationTarget = recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(w.getActivityRecord());
if (!w.canReceiveTouchInput() || !w.isVisible()
|| (w.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
- || unhandled.isEmpty()
- || ignoreRecentsAnimationTarget) {
+ || unhandled.isEmpty()) {
return;
}
@@ -6122,16 +6110,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void getKeepClearAreas(Set<Rect> outRestricted, Set<Rect> outUnrestricted) {
final Matrix tmpMatrix = new Matrix();
final float[] tmpFloat9 = new float[9];
- final RecentsAnimationController recentsAnimationController =
- mWmService.getRecentsAnimationController();
forAllWindows(w -> {
- // Skip the window if it is part of Recents animation
- final boolean ignoreRecentsAnimationTarget = recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(w.getActivityRecord());
- if (ignoreRecentsAnimationTarget) {
- return false; // continue traversal
- }
-
if (w.isVisible() && !w.inPinnedWindowingMode()) {
w.getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
@@ -6306,14 +6285,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
boolean updateDisplayOverrideConfigurationLocked() {
- // Preemptively cancel the running recents animation -- SysUI can't currently handle this
- // case properly since the signals it receives all happen post-change
- final RecentsAnimationController recentsAnimationController =
- mWmService.getRecentsAnimationController();
- if (recentsAnimationController != null) {
- recentsAnimationController.cancelAnimationForDisplayChange();
- }
-
Configuration values = new Configuration();
computeScreenConfiguration(values);
@@ -6914,79 +6885,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */
class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener {
- /**
- * The animating activity which shows the recents task list. It is set between
- * {@link RecentsAnimationController#initialize} and
- * {@link RecentsAnimationController#cleanupAnimation}.
- */
- private ActivityRecord mAnimatingRecents;
-
- /** Whether {@link #mAnimatingRecents} is going to be the top activity. */
- private boolean mRecentsWillBeTop;
-
FixedRotationTransitionListener(int displayId) {
super(displayId);
}
/**
- * If the recents activity has a fixed orientation which is different from the current top
- * activity, it will be rotated before being shown so we avoid a screen rotation animation
- * when showing the Recents view.
- */
- void onStartRecentsAnimation(@NonNull ActivityRecord r) {
- mAnimatingRecents = r;
- if (r.isVisible() && mFocusedApp != null && !mFocusedApp.occludesParent()) {
- // The recents activity has shown with the orientation determined by the top
- // activity, keep its current orientation to avoid flicking by the configuration
- // change of visible activity.
- return;
- }
- rotateInDifferentOrientationIfNeeded(r);
- if (r.hasFixedRotationTransform()) {
- // Set the record so we can recognize it to continue to update display orientation
- // if the recents activity becomes the top later.
- setFixedRotationLaunchingApp(r, r.getWindowConfiguration().getRotation());
- }
- }
-
- /**
- * If {@link #mAnimatingRecents} still has fixed rotation, it should be moved to top so we
- * don't clear {@link #mFixedRotationLaunchingApp} that will be handled by transition.
- */
- void onFinishRecentsAnimation() {
- final ActivityRecord animatingRecents = mAnimatingRecents;
- final boolean recentsWillBeTop = mRecentsWillBeTop;
- mAnimatingRecents = null;
- mRecentsWillBeTop = false;
- if (recentsWillBeTop) {
- // The recents activity will be the top, such as staying at recents list or
- // returning to home (if home and recents are the same activity).
- return;
- }
-
- if (animatingRecents != null && animatingRecents == mFixedRotationLaunchingApp
- && animatingRecents.isVisible() && animatingRecents != topRunningActivity()) {
- // The recents activity should be going to be invisible (switch to another app or
- // return to original top). Only clear the top launching record without finishing
- // the transform immediately because it won't affect display orientation. And before
- // the visibility is committed, the recents activity may perform relayout which may
- // cause unexpected configuration change if the rotated configuration is restored.
- // The transform will be finished when the transition is done.
- setFixedRotationLaunchingAppUnchecked(null);
- } else {
- // If there is already a launching activity that is not the recents, before its
- // transition is completed, the recents animation may be started. So if the recents
- // activity won't be the top, the display orientation should be updated according
- // to the current top activity.
- continueUpdateOrientationForDiffOrienLaunchingApp();
- }
- }
-
- void notifyRecentsWillBeTop() {
- mRecentsWillBeTop = true;
- }
-
- /**
* Returns {@code true} if the transient launch (e.g. recents animation) requested a fixed
* orientation, then the rotation change should be deferred.
*/
@@ -6996,8 +6899,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (hasFixedRotationTransientLaunch()) {
source = mFixedRotationLaunchingApp;
}
- } else if (mAnimatingRecents != null && !hasTopFixedRotationLaunchingApp()) {
- source = mAnimatingRecents;
}
if (source == null || source.getRequestedConfigurationOrientation(
true /* forDisplay */) == ORIENTATION_UNDEFINED) {
@@ -7010,19 +6911,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- // Ignore the animating recents so the fixed rotation transform won't be switched twice
- // by finishing the recents animation and moving it to top. That also avoids flickering
- // due to wait for previous activity to be paused if it supports PiP that ignores the
- // effect of resume-while-pausing.
- if (r == null || r == mAnimatingRecents) {
- return;
- }
- if (mAnimatingRecents != null && mRecentsWillBeTop) {
- // The activity is not the recents and it should be moved to back later, so it is
- // better to keep its current appearance for the next transition. Otherwise the
- // display orientation may be updated too early and the layout procedures at the
- // end of finishing recents animation is skipped. That causes flickering because
- // the surface of closing app hasn't updated to invisible.
+ if (r == null) {
return;
}
if (mFixedRotationLaunchingApp == null) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a5da5e7cc0de..5200e820fc02 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -611,16 +611,6 @@ public class DisplayRotation {
mDisplayRotationCoordinator.onDefaultDisplayRotationChanged(rotation);
}
- // Preemptively cancel the running recents animation -- SysUI can't currently handle this
- // case properly since the signals it receives all happen post-change. We do this earlier
- // in the rotation flow, since DisplayContent.updateDisplayOverrideConfigurationLocked seems
- // to happen too late.
- final RecentsAnimationController recentsAnimationController =
- mService.getRecentsAnimationController();
- if (recentsAnimationController != null) {
- recentsAnimationController.cancelAnimationForDisplayChange();
- }
-
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d rotation changed to %d from %d, lastOrientation=%d",
displayId, rotation, oldRotation, lastOrientation);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b8869f159169..ddbfd70ea4c4 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -49,7 +49,6 @@ import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS;
import static java.lang.Integer.MAX_VALUE;
import android.annotation.Nullable;
-import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
import android.os.IBinder;
@@ -111,7 +110,7 @@ final class InputMonitor {
* draw the live-tile above the recents activity, we also need to provide that activity as a
* z-layering reference so that we can place the recents input consumer above it.
*/
- private WeakReference<ActivityRecord> mActiveRecentsActivity = null;
+ private WeakReference<Task> mActiveRecentsTask = null;
private WeakReference<Task> mActiveRecentsLayerRef = null;
private class UpdateInputWindows implements Runnable {
@@ -388,13 +387,13 @@ final class InputMonitor {
/**
* Inform InputMonitor when recents is active so it can enable the recents input consumer.
- * @param activity The active recents activity. {@code null} means recents is not active.
+ * @param task The active recents task. {@code null} means recents is not active.
* @param layer A task whose Z-layer is used as a reference for how to sort the consumer.
*/
- void setActiveRecents(@Nullable ActivityRecord activity, @Nullable Task layer) {
- final boolean clear = activity == null;
- final boolean wasActive = mActiveRecentsActivity != null && mActiveRecentsLayerRef != null;
- mActiveRecentsActivity = clear ? null : new WeakReference<>(activity);
+ void setActiveRecents(@Nullable Task task, @Nullable Task layer) {
+ final boolean clear = task == null;
+ final boolean wasActive = mActiveRecentsTask != null && mActiveRecentsLayerRef != null;
+ mActiveRecentsTask = clear ? null : new WeakReference<>(task);
mActiveRecentsLayerRef = clear ? null : new WeakReference<>(layer);
if (clear && wasActive) {
setUpdateInputWindowsNeededLw();
@@ -413,17 +412,12 @@ final class InputMonitor {
// Request focus for the recents animation input consumer if an input consumer should
// be applied for the window.
if (recentsAnimationInputConsumer != null && focus != null) {
- final RecentsAnimationController recentsAnimationController =
- mService.getRecentsAnimationController();
// Apply recents input consumer when the focusing window is in recents animation.
- final boolean shouldApplyRecentsInputConsumer = (recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord))
- // Shell transitions doesn't use RecentsAnimationController but we still
- // have carryover legacy logic that relies on the consumer.
- || (getWeak(mActiveRecentsActivity) != null && focus.inTransition()
+ final boolean shouldApplyRecentsInputConsumer =
+ getWeak(mActiveRecentsTask) != null && focus.inTransition()
// only take focus from the recents activity to avoid intercepting
// events before the gesture officially starts.
- && focus.isActivityTypeHomeOrRecents());
+ && focus.isActivityTypeHomeOrRecents();
if (shouldApplyRecentsInputConsumer) {
if (mInputFocus != recentsAnimationInputConsumer.mWindowHandle.token) {
requestFocus(recentsAnimationInputConsumer.mWindowHandle.token,
@@ -569,7 +563,6 @@ final class InputMonitor {
private boolean mAddRecentsAnimationInputConsumerHandle;
private boolean mInDrag;
- private final Rect mTmpRect = new Rect();
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -586,18 +579,15 @@ final class InputMonitor {
resetInputConsumers(mInputTransaction);
// Update recents input consumer layer if active
- final ActivityRecord activeRecents = getWeak(mActiveRecentsActivity);
+ final Task activeRecents = getWeak(mActiveRecentsTask);
if (mAddRecentsAnimationInputConsumerHandle && activeRecents != null
&& activeRecents.getSurfaceControl() != null) {
WindowContainer layer = getWeak(mActiveRecentsLayerRef);
layer = layer != null ? layer : activeRecents;
// Handle edge-case for SUW where windows don't exist yet
if (layer.getSurfaceControl() != null) {
- final WindowState targetAppMainWindow = activeRecents.findMainWindow();
- if (targetAppMainWindow != null) {
- targetAppMainWindow.getBounds(mTmpRect);
- mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect);
- }
+ mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
+ activeRecents.getBounds());
mRecentsAnimationInputConsumer.show(mInputTransaction, layer);
mAddRecentsAnimationInputConsumerHandle = false;
}
@@ -629,24 +619,6 @@ final class InputMonitor {
return;
}
- // This only works for legacy transitions.
- final RecentsAnimationController recentsAnimationController =
- mService.getRecentsAnimationController();
- final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
- if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
- if (recentsAnimationController.updateInputConsumerForApp(
- mRecentsAnimationInputConsumer.mWindowHandle)) {
- final DisplayArea targetDA =
- recentsAnimationController.getTargetAppDisplayArea();
- if (targetDA != null) {
- mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA);
- mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 2);
- mAddRecentsAnimationInputConsumerHandle = false;
- }
- }
- }
-
if (w.inPinnedWindowingMode()) {
if (mAddPipInputConsumerHandle) {
final Task rootTask = w.getTask().getRootTask();
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 1a895ea22f36..403d3bd5c008 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -91,7 +91,6 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter {
return (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
|| transit == TRANSIT_OLD_WALLPAPER_CLOSE)
&& displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
- && service.getRecentsAnimationController() == null
&& displayContent.getAsyncRotationController() == null;
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index c592caf488ad..c06efc775403 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -17,82 +17,54 @@
package com.android.server.wm;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
-import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
-import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
-import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
-import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
-import static com.android.server.wm.TaskDisplayArea.getRootTaskAbove;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
-import android.os.RemoteException;
-import android.os.Trace;
import android.util.Slog;
-import android.view.IRecentsAnimationRunner;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
-import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
-import com.android.server.wm.TaskDisplayArea.OnRootTaskOrderChangedListener;
/**
* Manages the recents animation, including the reordering of the root tasks for the transition and
* cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
*/
-class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChangedListener {
+class RecentsAnimation {
private static final String TAG = RecentsAnimation.class.getSimpleName();
- private final ActivityTaskManagerService mService;
private final ActivityTaskSupervisor mTaskSupervisor;
private final ActivityStartController mActivityStartController;
- private final WindowManagerService mWindowManager;
private final TaskDisplayArea mDefaultTaskDisplayArea;
private final Intent mTargetIntent;
private final ComponentName mRecentsComponent;
private final @Nullable String mRecentsFeatureId;
private final int mRecentsUid;
- private final @Nullable WindowProcessController mCaller;
private final int mUserId;
private final int mTargetActivityType;
- /**
- * The activity which has been launched behind. We need to remember the activity because the
- * target root task may have other activities, then we are able to restore the launch-behind
- * state for the exact activity.
- */
- private ActivityRecord mLaunchedTargetActivity;
-
- // The root task to restore the target root task behind when the animation is finished
- private Task mRestoreTargetBehindRootTask;
-
RecentsAnimation(ActivityTaskManagerService atm, ActivityTaskSupervisor taskSupervisor,
- ActivityStartController activityStartController, WindowManagerService wm,
+ ActivityStartController activityStartController,
Intent targetIntent, ComponentName recentsComponent, @Nullable String recentsFeatureId,
- int recentsUid, @Nullable WindowProcessController caller) {
- mService = atm;
+ int recentsUid) {
mTaskSupervisor = taskSupervisor;
- mDefaultTaskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
+ mDefaultTaskDisplayArea = atm.mRootWindowContainer.getDefaultTaskDisplayArea();
mActivityStartController = activityStartController;
- mWindowManager = wm;
mTargetIntent = targetIntent;
mRecentsComponent = recentsComponent;
mRecentsFeatureId = recentsFeatureId;
mRecentsUid = recentsUid;
- mCaller = caller;
mUserId = atm.getCurrentUserId();
mTargetActivityType = targetIntent.getComponent() != null
&& recentsComponent.equals(targetIntent.getComponent())
@@ -171,310 +143,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
}
}
- void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner, long eventTime) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
-
- // Cancel any existing recents animation running synchronously (do not hold the
- // WM lock) before starting the newly requested recents animation as they can not coexist
- if (mWindowManager.getRecentsAnimationController() != null) {
- mWindowManager.getRecentsAnimationController().forceCancelAnimation(
- REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
- }
-
- // If the activity is associated with the root recents task, then try and get that first
- Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
- mTargetActivityType);
- ActivityRecord targetActivity = getTargetActivity(targetRootTask);
- final boolean hasExistingActivity = targetActivity != null;
- if (hasExistingActivity) {
- mRestoreTargetBehindRootTask = getRootTaskAbove(targetRootTask);
- if (mRestoreTargetBehindRootTask == null
- && targetRootTask.getTopMostTask() == targetActivity.getTask()) {
- notifyAnimationCancelBeforeStart(recentsAnimationRunner);
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "No root task above target root task=%s", targetRootTask);
- return;
- }
- }
-
- // Send launch hint if we are actually launching the target. If it's already visible
- // (shouldn't happen in general) we don't need to send it.
- if (targetActivity == null || !targetActivity.isVisibleRequested()) {
- mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(
- true /* forceSend */, targetActivity);
- }
-
- final LaunchingState launchingState =
- mTaskSupervisor.getActivityMetricsLogger().notifyActivityLaunching(mTargetIntent);
-
- setProcessAnimating(true);
-
- mService.deferWindowLayout();
- try {
- if (hasExistingActivity) {
- // Move the recents activity into place for the animation if it is not top most
- mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(targetRootTask);
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved rootTask=%s behind rootTask=%s",
- targetRootTask, getRootTaskAbove(targetRootTask));
-
- // If there are multiple tasks in the target root task (ie. the root home task,
- // with 3p and default launchers coexisting), then move the task to the top as a
- // part of moving the root task to the front
- final Task task = targetActivity.getTask();
- if (targetRootTask.getTopMostTask() != task) {
- targetRootTask.positionChildAtTop(task);
- }
- } else {
- // No recents activity, create the new recents activity bottom most
- startRecentsActivityInBackground("startRecentsActivity_noTargetActivity");
-
- // Move the recents activity into place for the animation
- targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
- mTargetActivityType);
- targetActivity = getTargetActivity(targetRootTask);
- mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(targetRootTask);
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved rootTask=%s behind rootTask=%s",
- targetRootTask, getRootTaskAbove(targetRootTask));
-
- mWindowManager.prepareAppTransitionNone();
- mWindowManager.executeAppTransition();
-
- // TODO: Maybe wait for app to draw in this particular case?
-
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Started intent=%s", mTargetIntent);
- }
-
- // Mark the target activity as launch-behind to bump its visibility for the
- // duration of the gesture that is driven by the recents component
- targetActivity.mLaunchTaskBehind = true;
- mLaunchedTargetActivity = targetActivity;
- // TODO(b/156772625): Evaluate to send new intents vs. replacing the intent extras.
- targetActivity.intent.replaceExtras(mTargetIntent);
-
- // Fetch all the surface controls and pass them to the client to get the animation
- // started
- mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
- this, mDefaultTaskDisplayArea.getDisplayId(),
- mTaskSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
-
- // If we updated the launch-behind state, update the visibility of the activities after
- // we fetch the visible tasks to be controlled by the animation
- mService.mRootWindowContainer.ensureActivitiesVisible();
-
- ActivityOptions options = null;
- if (eventTime > 0) {
- options = ActivityOptions.makeBasic();
- options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
- }
- mTaskSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState,
- START_TASK_TO_FRONT, !hasExistingActivity, targetActivity, options);
-
- // Register for root task order changes
- mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(this);
- } catch (Exception e) {
- Slog.e(TAG, "Failed to start recents activity", e);
- throw e;
- } finally {
- mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode,
- boolean sendUserLeaveHint) {
- synchronized (mService.mGlobalLock) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "onAnimationFinished(): controller=%s reorderMode=%d",
- mWindowManager.getRecentsAnimationController(), reorderMode);
-
- // Unregister for root task order changes
- mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(this);
-
- final RecentsAnimationController controller =
- mWindowManager.getRecentsAnimationController();
- if (controller == null) return;
-
- // Just to be sure end the launch hint in case the target activity was never launched.
- // However, if we're keeping the activity and making it visible, we can leave it on.
- if (reorderMode != REORDER_KEEP_IN_PLACE) {
- mService.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
- }
-
- // Once the target is shown, prevent spurious background app switches
- if (reorderMode == REORDER_MOVE_TO_TOP) {
- mService.stopAppSwitches();
- }
-
- inSurfaceTransaction(() -> {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
- mService.deferWindowLayout();
- try {
- mWindowManager.cleanupRecentsAnimation(reorderMode);
-
- final Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(
- WINDOWING_MODE_UNDEFINED, mTargetActivityType);
- // Prefer to use the original target activity instead of top activity because
- // we may have moved another task to top (starting 3p launcher).
- final ActivityRecord targetActivity = targetRootTask != null
- ? targetRootTask.isInTask(mLaunchedTargetActivity)
- : null;
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "onAnimationFinished(): targetRootTask=%s targetActivity=%s "
- + "mRestoreTargetBehindRootTask=%s",
- targetRootTask, targetActivity, mRestoreTargetBehindRootTask);
- if (targetActivity == null) {
- return;
- }
-
- // Restore the launched-behind state
- targetActivity.mLaunchTaskBehind = false;
-
- if (reorderMode == REORDER_MOVE_TO_TOP) {
- // Bring the target root task to the front
- mTaskSupervisor.mNoAnimActivities.add(targetActivity);
-
- if (sendUserLeaveHint) {
- // Setting this allows the previous app to PiP.
- mTaskSupervisor.mUserLeaving = true;
- targetRootTask.moveTaskToFront(targetActivity.getTask(),
- true /* noAnimation */, null /* activityOptions */,
- targetActivity.appTimeTracker,
- "RecentsAnimation.onAnimationFinished()");
- } else {
- targetRootTask.moveToFront("RecentsAnimation.onAnimationFinished()");
- }
-
- if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
- final Task topRootTask = getTopNonAlwaysOnTopRootTask();
- if (topRootTask != targetRootTask) {
- ProtoLog.w(WM_DEBUG_RECENTS_ANIMATIONS,
- "Expected target rootTask=%s"
- + " to be top most but found rootTask=%s",
- targetRootTask, topRootTask);
- }
- }
- } else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
- // Restore the target root task to its previous position
- final TaskDisplayArea taskDisplayArea = targetActivity.getDisplayArea();
- taskDisplayArea.moveRootTaskBehindRootTask(targetRootTask,
- mRestoreTargetBehindRootTask);
- if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
- final Task aboveTargetRootTask = getRootTaskAbove(targetRootTask);
- if (mRestoreTargetBehindRootTask != null
- && aboveTargetRootTask != mRestoreTargetBehindRootTask) {
- ProtoLog.w(WM_DEBUG_RECENTS_ANIMATIONS,
- "Expected target rootTask=%s to restored behind "
- + "rootTask=%s but it is behind rootTask=%s",
- targetRootTask, mRestoreTargetBehindRootTask,
- aboveTargetRootTask);
- }
- }
- } else {
- // If there is no recents screenshot animation, we can update the visibility
- // of target root task immediately because it is visually invisible and the
- // launch-behind state is restored. That also prevents the next transition
- // type being disturbed if the visibility is updated after setting the next
- // transition (the target activity will be one of closing apps).
- if (!controller.shouldDeferCancelWithScreenshot()
- && !targetRootTask.isFocusedRootTaskOnDisplay()) {
- targetRootTask.ensureActivitiesVisible(null /* starting */);
- }
- // Keep target root task in place, nothing changes, so ignore the transition
- // logic below
- return;
- }
-
- mWindowManager.prepareAppTransitionNone();
- mService.mRootWindowContainer.ensureActivitiesVisible();
- mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
-
- // No reason to wait for the pausing activity in this case, as the hiding of
- // surfaces needs to be done immediately.
- mWindowManager.executeAppTransition();
-
- final Task rootTask = targetRootTask.getRootTask();
- // Client state may have changed during the recents animation, so force
- // send task info so the client can synchronize its state.
- rootTask.dispatchTaskInfoChangedIfNeeded(true /* force */);
- } catch (Exception e) {
- Slog.e(TAG, "Failed to clean up recents activity", e);
- throw e;
- } finally {
- mTaskSupervisor.mUserLeaving = false;
- mService.continueWindowLayout();
- // Make sure the surfaces are updated with the latest state. Sometimes the
- // surface placement may be skipped if display configuration is changed (i.e.
- // {@link DisplayContent#mWaitingForConfig} is true).
- if (mWindowManager.mRoot.isLayoutNeeded()) {
- mWindowManager.mRoot.performSurfacePlacement();
- }
- setProcessAnimating(false);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- });
- }
- }
-
- // No-op wrapper to keep legacy code.
- private static void inSurfaceTransaction(Runnable exec) {
- exec.run();
- }
-
- /** Gives the owner of recents animation higher priority. */
- private void setProcessAnimating(boolean animating) {
- if (mCaller == null) return;
- // Apply the top-app scheduling group to who runs the animation.
- mCaller.setRunningRecentsAnimation(animating);
- int demoteReasons = mService.mDemoteTopAppReasons;
- if (animating) {
- demoteReasons |= ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS;
- } else {
- demoteReasons &= ~ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS;
- }
- mService.mDemoteTopAppReasons = demoteReasons;
- // Make the demotion of the real top app take effect. No need to restore top app state for
- // finishing recents because addToStopping -> scheduleIdle -> activityIdleInternal ->
- // trimApplications will have a full update.
- if (animating && mService.mTopApp != null) {
- mService.mTopApp.scheduleUpdateOomAdj();
- }
- }
-
- @Override
- public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
- boolean sendUserLeaveHint) {
- finishAnimation(reorderMode, sendUserLeaveHint);
- }
-
- @Override
- public void onRootTaskOrderChanged(Task rootTask) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "onRootTaskOrderChanged(): rootTask=%s", rootTask);
- if (mDefaultTaskDisplayArea.getRootTask(t -> t == rootTask) == null
- || !rootTask.shouldBeVisible(null)) {
- // The root task is not visible, so ignore this change
- return;
- }
- final RecentsAnimationController controller =
- mWindowManager.getRecentsAnimationController();
- if (controller == null) {
- return;
- }
-
- // We defer canceling the recents animation until the next app transition in the following
- // cases:
- // 1) The next launching task is not being animated by the recents animation
- // 2) The next task is home activity. (i.e. pressing home key to back home in recents).
- if ((!controller.isAnimatingTask(rootTask.getTopMostTask())
- || controller.isTargetApp(rootTask.getTopNonFinishingActivity()))
- && controller.shouldDeferCancelUntilNextTransition()) {
- // Always prepare an app transition since we rely on the transition callbacks to cleanup
- mWindowManager.prepareAppTransitionNone();
- controller.setCancelOnNextTransitionStart();
- }
- }
-
private void startRecentsActivityInBackground(String reason) {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchActivityType(mTargetActivityType);
@@ -492,26 +160,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
}
/**
- * Called only when the animation should be canceled prior to starting.
- */
- static void notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner) {
- try {
- recentsAnimationRunner.onAnimationCanceled(null /* taskIds */,
- null /* taskSnapshots */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to cancel recents animation before start", e);
- }
- }
-
- /**
- * @return The top root task that is not always-on-top.
- */
- private Task getTopNonAlwaysOnTopRootTask() {
- return mDefaultTaskDisplayArea.getRootTask(task ->
- !task.getWindowConfiguration().isAlwaysOnTop());
- }
-
- /**
* @return the top activity in the {@param targetRootTask} matching the {@param component},
* or just the top activity of the top task if no task matches the component.
*/
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
deleted file mode 100644
index 6f947135b789..000000000000
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ /dev/null
@@ -1,1382 +0,0 @@
-/*
- * Copyright (C) 2017 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.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
-import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.app.WindowConfiguration;
-import android.graphics.GraphicBuffer;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder.DeathRecipient;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.proto.ProtoOutputStream;
-import android.view.IRecentsAnimationController;
-import android.view.IRecentsAnimationRunner;
-import android.view.InputWindowHandle;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceSession;
-import android.view.WindowInsets.Type;
-import android.window.PictureInPictureSurfaceTransaction;
-import android.window.TaskSnapshot;
-import android.window.WindowAnimationState;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.IResultReceiver;
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.LocalServices;
-import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-import com.android.server.wm.utils.InsetUtils;
-
-import com.google.android.collect.Sets;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.stream.Collectors;
-
-/**
- * Controls a single instance of the remote driven recents animation. In particular, this allows
- * the calling SystemUI to animate the visible task windows as a part of the transition. The remote
- * runner is provided an animation controller which allows it to take screenshots and to notify
- * window manager when the animation is completed. In addition, window manager may also notify the
- * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.)
- */
-public class RecentsAnimationController implements DeathRecipient {
- private static final String TAG = RecentsAnimationController.class.getSimpleName();
- private static final long FAILSAFE_DELAY = 1000;
-
- // Constant for a yet-to-be-calculated {@link RemoteAnimationTarget#Mode} state
- private static final int MODE_UNKNOWN = -1;
-
- public static final int REORDER_KEEP_IN_PLACE = 0;
- public static final int REORDER_MOVE_TO_TOP = 1;
- public static final int REORDER_MOVE_TO_ORIGINAL_POSITION = 2;
-
- @IntDef(prefix = { "REORDER_MODE_" }, value = {
- REORDER_KEEP_IN_PLACE,
- REORDER_MOVE_TO_TOP,
- REORDER_MOVE_TO_ORIGINAL_POSITION
- })
- public @interface ReorderMode {}
-
- private final WindowManagerService mService;
- @VisibleForTesting
- final StatusBarManagerInternal mStatusBar;
- private IRecentsAnimationRunner mRunner;
- private final RecentsAnimationCallbacks mCallbacks;
- private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
- private final IntArray mPendingNewTaskTargets = new IntArray(0);
-
- private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
- new ArrayList<>();
- private final int mDisplayId;
- private boolean mWillFinishToHome = false;
- private final Runnable mFailsafeRunnable = this::onFailsafe;
-
- // The recents component app token that is shown behind the visible tasks
- private ActivityRecord mTargetActivityRecord;
- private DisplayContent mDisplayContent;
- private int mTargetActivityType;
-
- // We start the RecentsAnimationController in a pending-start state since we need to wait for
- // the wallpaper/activity to draw before we can give control to the handler to start animating
- // the visible task surfaces
- private boolean mPendingStart = true;
-
- // Set when the animation has been canceled
- private boolean mCanceled;
-
- // Whether or not the input consumer is enabled. The input consumer must be both registered and
- // enabled for it to start intercepting touch events.
- private boolean mInputConsumerEnabled;
-
- private final Rect mTmpRect = new Rect();
-
- private boolean mLinkedToDeathOfRunner;
-
- // Whether to try to defer canceling from a root task order change until the next transition
- private boolean mRequestDeferCancelUntilNextTransition;
- // Whether to actually defer canceling until the next transition
- private boolean mCancelOnNextTransitionStart;
- // Whether to take a screenshot when handling a deferred cancel
- private boolean mCancelDeferredWithScreenshot;
- // The reorder mode to apply after the cleanupScreenshot() callback
- private int mPendingCancelWithScreenshotReorderMode = REORDER_MOVE_TO_ORIGINAL_POSITION;
-
- @VisibleForTesting
- boolean mIsAddingTaskToTargets;
- private boolean mNavigationBarAttachedToApp;
- private ActivityRecord mNavBarAttachedApp;
-
- private final ArrayList<RemoteAnimationTarget> mPendingTaskAppears = new ArrayList<>();
-
- /**
- * An app transition listener to cancel the recents animation only after the app transition
- * starts or is canceled.
- */
- final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
- @Override
- public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
- continueDeferredCancel();
- return 0;
- }
-
- @Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
- continueDeferredCancel();
- }
-
- private void continueDeferredCancel() {
- mDisplayContent.mAppTransition.unregisterListener(this);
- if (mCanceled) {
- return;
- }
-
- if (mCancelOnNextTransitionStart) {
- mCancelOnNextTransitionStart = false;
- cancelAnimationWithScreenshot(mCancelDeferredWithScreenshot);
- }
- }
- };
-
- public interface RecentsAnimationCallbacks {
- /** Callback when recents animation is finished. */
- void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint);
- }
-
- private final IRecentsAnimationController mController =
- new IRecentsAnimationController.Stub() {
-
- @Override
- public TaskSnapshot screenshotTask(int taskId) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "screenshotTask(%d): mCanceled=%b", taskId, mCanceled);
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mService.getWindowManagerLock()) {
- if (mCanceled) {
- return null;
- }
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
- final Task task = adapter.mTask;
- if (task.mTaskId == taskId) {
- final TaskSnapshotController snapshotController =
- mService.mTaskSnapshotController;
- final ArraySet<Task> tasks = Sets.newArraySet(task);
- snapshotController.snapshotTasks(tasks);
- snapshotController.addSkipClosingAppSnapshotTasks(tasks);
- return snapshotController.getSnapshot(taskId, task.mUserId,
- false /* restoreFromDisk */, false /* isLowResolution */);
- }
- }
- return null;
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setFinishTaskTransaction(int taskId,
- PictureInPictureSurfaceTransaction finishTransaction,
- SurfaceControl overlay) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "setFinishTaskTransaction(%d): transaction=%s", taskId, finishTransaction);
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mService.getWindowManagerLock()) {
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
- if (taskAdapter.mTask.mTaskId == taskId) {
- taskAdapter.mFinishTransaction = finishTransaction;
- taskAdapter.mFinishOverlay = overlay;
- break;
- }
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void finish(boolean moveHomeToTop, boolean sendUserLeaveHint,
- IResultReceiver finishCb) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "finish(%b): mCanceled=%b", moveHomeToTop, mCanceled);
- final long token = Binder.clearCallingIdentity();
- try {
- // Note, the callback will handle its own synchronization, do not lock on WM lock
- // prior to calling the callback
- mCallbacks.onAnimationFinished(moveHomeToTop
- ? REORDER_MOVE_TO_TOP
- : REORDER_MOVE_TO_ORIGINAL_POSITION, sendUserLeaveHint);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- if (finishCb != null) {
- try {
- finishCb.send(0, new Bundle());
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to report animation finished", e);
- }
- }
- }
-
- @Override
- public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars)
- throws RemoteException {
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mService.getWindowManagerLock()) {
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final Task task = mPendingAnimations.get(i).mTask;
- if (task.getActivityType() != mTargetActivityType) {
- task.setCanAffectSystemUiFlags(behindSystemBars);
- }
- }
- InputMethodManagerInternal.get().maybeFinishStylusHandwriting();
- mService.mWindowPlacerLocked.requestTraversal();
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setInputConsumerEnabled(boolean enabled) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "setInputConsumerEnabled(%s): mCanceled=%b", enabled, mCanceled);
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mService.getWindowManagerLock()) {
- if (mCanceled) {
- return;
- }
- mInputConsumerEnabled = enabled;
- final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
- inputMonitor.updateInputWindowsLw(true /*force*/);
- mService.scheduleAnimationLocked();
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
- synchronized (mService.mGlobalLock) {
- setDeferredCancel(defer, screenshot);
- }
- }
-
- @Override
- public void cleanupScreenshot() {
- final long token = Binder.clearCallingIdentity();
- try {
- // Note, the callback will handle its own synchronization, do not lock on WM lock
- // prior to calling the callback
- continueDeferredCancelAnimation();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setWillFinishToHome(boolean willFinishToHome) {
- synchronized (mService.getWindowManagerLock()) {
- RecentsAnimationController.this.setWillFinishToHome(willFinishToHome);
- }
- }
-
- @Override
- public boolean removeTask(int taskId) {
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mService.getWindowManagerLock()) {
- return removeTaskInternal(taskId);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void detachNavigationBarFromApp(boolean moveHomeToTop) {
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mService.getWindowManagerLock()) {
- restoreNavigationBarFromApp(
- moveHomeToTop || mIsAddingTaskToTargets /* animate */);
- mService.mWindowPlacerLocked.requestTraversal();
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void animateNavigationBarToApp(long duration) {
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mService.getWindowManagerLock()) {
- animateNavigationBarForAppLaunch(duration);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void handOffAnimation(
- RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
- // unused legacy implementation
- }
- };
-
- /**
- * @param remoteAnimationRunner The remote runner which should be notified when the animation is
- * ready to start or has been canceled
- * @param callbacks Callbacks to be made when the animation finishes
- */
- RecentsAnimationController(WindowManagerService service,
- IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks,
- int displayId) {
- mService = service;
- mRunner = remoteAnimationRunner;
- mCallbacks = callbacks;
- mDisplayId = displayId;
- mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
- mDisplayContent = service.mRoot.getDisplayContent(displayId);
- }
-
- /**
- * Initializes the recents animation controller. This is a separate call from the constructor
- * because it may call cancelAnimation() which needs to properly clean up the controller
- * in the window manager.
- */
- public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds,
- ActivityRecord targetActivity) {
- mTargetActivityType = targetActivityType;
- mDisplayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
-
- // Make leashes for each of the visible/target tasks and add it to the recents animation to
- // be started
- // TODO(b/153090560): Support Recents on multiple task display areas
- final ArrayList<Task> visibleTasks = mDisplayContent.getDefaultTaskDisplayArea()
- .getVisibleTasks();
- final Task targetRootTask = mDisplayContent.getDefaultTaskDisplayArea()
- .getRootTask(WINDOWING_MODE_UNDEFINED, targetActivityType);
- if (targetRootTask != null) {
- targetRootTask.forAllLeafTasks(t -> {
- if (!visibleTasks.contains(t)) {
- visibleTasks.add(t);
- }
- }, true /* traverseTopToBottom */);
- }
-
- final int taskCount = visibleTasks.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- final Task task = visibleTasks.get(i);
- if (skipAnimation(task)) {
- continue;
- }
- addAnimation(task, !recentTaskIds.get(task.mTaskId), false /* hidden */,
- (type, anim) -> task.forAllWindows(win -> {
- win.onAnimationFinished(type, anim);
- }, true /* traverseTopToBottom */));
- }
-
- // Skip the animation if there is nothing to animate
- if (mPendingAnimations.isEmpty()) {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-noVisibleTasks");
- return;
- }
-
- try {
- linkToDeathOfRunner();
- } catch (RemoteException e) {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-failedToLinkToDeath");
- return;
- }
-
- attachNavigationBarToApp();
-
- // Adjust the wallpaper visibility for the showing target activity
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "setHomeApp(%s)", targetActivity.getName());
- mTargetActivityRecord = targetActivity;
- if (targetActivity.windowsCanBeWallpaperTarget()) {
- mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- mDisplayContent.setLayoutNeeded();
- }
-
- mService.mWindowPlacerLocked.performSurfacePlacement();
-
- mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity);
-
- // Notify that the animation has started
- if (mStatusBar != null) {
- mStatusBar.onRecentsAnimationStateChanged(true /* running */);
- }
- }
-
- /**
- * Return whether the given window should still be considered interesting for the all-drawn
- * state. This is only interesting for the target app, which may have child windows that are
- * not actually visible and should not be considered interesting and waited upon.
- */
- protected boolean isInterestingForAllDrawn(WindowState window) {
- if (isTargetApp(window.getActivityRecord())) {
- if (window.getWindowType() != TYPE_BASE_APPLICATION
- && window.getAttrs().alpha == 0f) {
- // If there is a cihld window that is alpha 0, then ignore that window
- return false;
- }
- }
- // By default all windows are still interesting for all drawn purposes
- return true;
- }
-
- /**
- * Whether a task should be filtered from the recents animation. This can be true for tasks
- * being displayed outside of recents.
- */
- private boolean skipAnimation(Task task) {
- final WindowConfiguration config = task.getWindowConfiguration();
- return task.isAlwaysOnTop() || config.tasksAreFloating();
- }
-
- @VisibleForTesting
- TaskAnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
- return addAnimation(task, isRecentTaskInvisible, false /* hidden */,
- null /* finishedCallback */);
- }
-
- @VisibleForTesting
- TaskAnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, boolean hidden,
- OnAnimationFinishedCallback finishedCallback) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName());
- final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
- isRecentTaskInvisible);
- task.startAnimation(task.getPendingTransaction(), taskAdapter, hidden,
- ANIMATION_TYPE_RECENTS, finishedCallback);
- task.commitPendingTransaction();
- mPendingAnimations.add(taskAdapter);
- return taskAdapter;
- }
-
- @VisibleForTesting
- void removeAnimation(TaskAnimationAdapter taskAdapter) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "removeAnimation(%d)", taskAdapter.mTask.mTaskId);
- taskAdapter.onRemove();
- mPendingAnimations.remove(taskAdapter);
- }
-
- @VisibleForTesting
- void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeWallpaperAnimation()");
- wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(
- wallpaperAdapter.getLastAnimationType(), wallpaperAdapter);
- mPendingWallpaperAnimations.remove(wallpaperAdapter);
- }
-
- void startAnimation() {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "startAnimation(): mPendingStart=%b mCanceled=%b", mPendingStart, mCanceled);
- if (!mPendingStart || mCanceled) {
- // Skip starting if we've already started or canceled the animation
- return;
- }
- try {
- // Create the app targets
- final RemoteAnimationTarget[] appTargets = createAppAnimations();
-
- // Skip the animation if there is nothing to animate
- if (appTargets.length == 0) {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows");
- return;
- }
-
- // Create the wallpaper targets
- final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
-
- mPendingStart = false;
-
- final Rect contentInsets;
- final WindowState targetAppMainWindow = getTargetAppMainWindow();
- if (targetAppMainWindow != null) {
- contentInsets = targetAppMainWindow
- .getInsetsStateWithVisibilityOverride()
- .calculateInsets(mTargetActivityRecord.getBounds(), Type.systemBars(),
- false /* ignoreVisibility */).toRect();
- } else {
- // If the window for the activity had not yet been created, use the display insets.
- mService.getStableInsets(mDisplayId, mTmpRect);
- contentInsets = mTmpRect;
- }
- mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets,
- null, new Bundle());
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "startAnimation(): Notify animation start: %s",
- mPendingAnimations.stream()
- .map(anim->anim.mTask.mTaskId).collect(Collectors.toList()));
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to start recents animation", e);
- }
-
- if (mTargetActivityRecord != null) {
- final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(1);
- reasons.put(mTargetActivityRecord, APP_TRANSITION_RECENTS_ANIM);
- mService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
- .notifyTransitionStarting(reasons);
- }
- }
-
- boolean isNavigationBarAttachedToApp() {
- return mNavigationBarAttachedToApp;
- }
-
- @VisibleForTesting
- WindowState getNavigationBarWindow() {
- return mDisplayContent.getDisplayPolicy().getNavigationBar();
- }
-
- private void attachNavigationBarToApp() {
- if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
- // Skip the case where the nav bar is controlled by fade rotation.
- || mDisplayContent.getAsyncRotationController() != null) {
- return;
- }
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
- final Task task = adapter.mTask;
- if (task.isActivityTypeHomeOrRecents()) {
- continue;
- }
- mNavBarAttachedApp = task.getTopVisibleActivity();
- break;
- }
-
- final WindowState navWindow = getNavigationBarWindow();
- if (mNavBarAttachedApp == null || navWindow == null || navWindow.mToken == null) {
- return;
- }
- mNavigationBarAttachedToApp = true;
- navWindow.mToken.cancelAnimation();
- final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
- final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
- navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
- t.reparent(navSurfaceControl, mNavBarAttachedApp.getSurfaceControl());
- t.show(navSurfaceControl);
-
- final WindowContainer imeContainer = mDisplayContent.getImeContainer();
- if (imeContainer.isVisible()) {
- t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
- } else {
- // Place the nav bar on top of anything else in the top activity.
- t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
- }
- if (mStatusBar != null) {
- mStatusBar.setNavigationBarLumaSamplingEnabled(mDisplayId, false);
- }
- }
-
- @VisibleForTesting
- void restoreNavigationBarFromApp(boolean animate) {
- if (!mNavigationBarAttachedToApp) {
- return;
- }
- mNavigationBarAttachedToApp = false;
-
- if (mStatusBar != null) {
- mStatusBar.setNavigationBarLumaSamplingEnabled(mDisplayId, true);
- }
-
- final WindowState navWindow = getNavigationBarWindow();
- if (navWindow == null) {
- return;
- }
- navWindow.setSurfaceTranslationY(0);
-
- final WindowToken navToken = navWindow.mToken;
- if (navToken == null) {
- return;
- }
- final SurfaceControl.Transaction t = mDisplayContent.getPendingTransaction();
- final WindowContainer parent = navToken.getParent();
- t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
-
- if (animate) {
- final NavBarFadeAnimationController controller =
- new NavBarFadeAnimationController(mDisplayContent);
- controller.fadeWindowToken(true);
- } else {
- // Reparent the SurfaceControl of nav bar token back.
- t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
- }
- }
-
- void animateNavigationBarForAppLaunch(long duration) {
- if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
- // Skip the case where the nav bar is controlled by fade rotation.
- || mDisplayContent.getAsyncRotationController() != null
- || mNavigationBarAttachedToApp
- || mNavBarAttachedApp == null) {
- return;
- }
-
- final NavBarFadeAnimationController controller =
- new NavBarFadeAnimationController(mDisplayContent);
- controller.fadeOutAndInSequentially(duration, null /* fadeOutParent */,
- mNavBarAttachedApp.getSurfaceControl());
- }
-
- void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
- if (mRunner != null) {
- mIsAddingTaskToTargets = task != null;
- mNavBarAttachedApp = task == null ? null : task.getTopVisibleActivity();
- // No need to send task appeared when the task target already exists, or when the
- // task is being managed as a multi-window mode outside of recents (e.g. bubbles).
- if (isAnimatingTask(task) || skipAnimation(task)) {
- return;
- }
- collectTaskRemoteAnimations(task, MODE_OPENING, finishedCallback);
- }
- }
-
- void sendTasksAppeared() {
- if (mPendingTaskAppears.isEmpty() || mRunner == null) return;
- try {
- final RemoteAnimationTarget[] targets = mPendingTaskAppears.toArray(
- new RemoteAnimationTarget[0]);
- mRunner.onTasksAppeared(targets);
- mPendingTaskAppears.clear();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to report task appeared", e);
- }
- }
-
- private void collectTaskRemoteAnimations(Task task, int mode,
- OnAnimationFinishedCallback finishedCallback) {
- final SparseBooleanArray recentTaskIds =
- mService.mAtmService.getRecentTasks().getRecentTaskIds();
-
- // The target must be built off the root task (the leaf task surface would be cropped
- // within the root surface). However, recents only tracks leaf task ids, so we'll traverse
- // and create animation target for all visible leaf tasks.
- task.forAllLeafTasks(leafTask -> {
- if (!leafTask.shouldBeVisible(null /* starting */)) {
- return;
- }
- final int taskId = leafTask.mTaskId;
- TaskAnimationAdapter adapter = addAnimation(leafTask,
- !recentTaskIds.get(taskId), true /* hidden */, finishedCallback);
- mPendingNewTaskTargets.add(taskId);
- final RemoteAnimationTarget target =
- adapter.createRemoteAnimationTarget(taskId, mode);
- if (target != null) {
- mPendingTaskAppears.add(target);
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "collectTaskRemoteAnimations, target: %s", target);
- }
- }, false /* traverseTopToBottom */);
- }
-
- private boolean removeTaskInternal(int taskId) {
- boolean result = false;
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- // Only allows when task target has became visible to user, to prevent
- // the flickering during remove animation and task visible.
- final TaskAnimationAdapter target = mPendingAnimations.get(i);
- if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) {
- removeAnimation(target);
- final int taskIndex = mPendingNewTaskTargets.indexOf(taskId);
- if (taskIndex != -1) {
- mPendingNewTaskTargets.remove(taskIndex);
- }
- result = true;
- break;
- }
- }
- return result;
- }
-
- private RemoteAnimationTarget[] createAppAnimations() {
- final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
- final RemoteAnimationTarget target =
- taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID, MODE_UNKNOWN);
- if (target != null) {
- targets.add(target);
- } else {
- removeAnimation(taskAdapter);
- }
- }
- return targets.toArray(new RemoteAnimationTarget[targets.size()]);
- }
-
- private RemoteAnimationTarget[] createWallpaperAnimations() {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, 0L, 0L,
- adapter -> {
- synchronized (mService.mGlobalLock) {
- // If the wallpaper animation is canceled, continue with the recents
- // animation
- mPendingWallpaperAnimations.remove(adapter);
- }
- }, mPendingWallpaperAnimations);
- }
-
- void forceCancelAnimation(@ReorderMode int reorderMode, String reason) {
- if (!mCanceled) {
- cancelAnimation(reorderMode, reason);
- } else {
- continueDeferredCancelAnimation();
- }
- }
-
- void cancelAnimation(@ReorderMode int reorderMode, String reason) {
- cancelAnimation(reorderMode, false /*screenshot */, reason);
- }
-
- void cancelAnimationWithScreenshot(boolean screenshot) {
- cancelAnimation(REORDER_KEEP_IN_PLACE, screenshot, "rootTaskOrderChanged");
- }
-
- /**
- * Cancels the running animation when starting home, providing a snapshot for the runner to
- * properly handle the cancellation. This call uses the provided hint to determine how to
- * finish the animation.
- */
- public void cancelAnimationForHomeStart() {
- final int reorderMode = mTargetActivityType == ACTIVITY_TYPE_HOME && mWillFinishToHome
- ? REORDER_MOVE_TO_TOP
- : REORDER_KEEP_IN_PLACE;
- cancelAnimation(reorderMode, true /* screenshot */, "cancelAnimationForHomeStart");
- }
-
- /**
- * Cancels the running animation when there is a display change, providing a snapshot for the
- * runner to properly handle the cancellation. This call uses the provided hint to determine
- * how to finish the animation.
- */
- public void cancelAnimationForDisplayChange() {
- cancelAnimation(mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
- true /* screenshot */, "cancelAnimationForDisplayChange");
- }
-
- private void cancelAnimation(@ReorderMode int reorderMode, boolean screenshot, String reason) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
- synchronized (mService.getWindowManagerLock()) {
- if (mCanceled) {
- // We've already canceled the animation
- return;
- }
- mService.mH.removeCallbacks(mFailsafeRunnable);
- mCanceled = true;
-
- if (screenshot && !mPendingAnimations.isEmpty()) {
- final ArrayMap<Task, TaskSnapshot> snapshotMap = screenshotRecentTasks();
- mPendingCancelWithScreenshotReorderMode = reorderMode;
-
- if (!snapshotMap.isEmpty()) {
- try {
- int[] taskIds = new int[snapshotMap.size()];
- TaskSnapshot[] snapshots = new TaskSnapshot[snapshotMap.size()];
- for (int i = snapshotMap.size() - 1; i >= 0; i--) {
- taskIds[i] = snapshotMap.keyAt(i).mTaskId;
- snapshots[i] = snapshotMap.valueAt(i);
- }
- mRunner.onAnimationCanceled(taskIds, snapshots);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to cancel recents animation", e);
- }
- // Schedule a new failsafe for if the runner doesn't clean up the screenshot
- scheduleFailsafe();
- return;
- }
- // Fallback to a normal cancel since we couldn't screenshot
- }
-
- // Notify the runner and clean up the animation immediately
- // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls
- // to the runner if we this actually triggers cancel twice on the caller
- try {
- mRunner.onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to cancel recents animation", e);
- }
- mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
- }
- }
-
- @VisibleForTesting
- void continueDeferredCancelAnimation() {
- mCallbacks.onAnimationFinished(mPendingCancelWithScreenshotReorderMode,
- false /* sendUserLeaveHint */);
- }
-
- @VisibleForTesting
- void setWillFinishToHome(boolean willFinishToHome) {
- mWillFinishToHome = willFinishToHome;
- }
-
- /**
- * Cancel recents animation when the next app transition starts.
- * <p>
- * When we cancel the recents animation due to a root task order change, we can't just cancel it
- * immediately as it would lead to a flicker in Launcher if we just remove the task from the
- * leash. Instead we screenshot the previous task and replace the child of the leash with the
- * screenshot, so that Launcher can still control the leash lifecycle & make the next app
- * transition animate smoothly without flickering.
- */
- void setCancelOnNextTransitionStart() {
- mCancelOnNextTransitionStart = true;
- }
-
- /**
- * Requests that we attempt to defer the cancel until the next app transition if we are
- * canceling from a root task order change. If {@param screenshot} is specified, then the
- * system will replace the contents of the leash with a screenshot, which must be cleaned up
- * when the runner calls cleanUpScreenshot().
- */
- void setDeferredCancel(boolean defer, boolean screenshot) {
- mRequestDeferCancelUntilNextTransition = defer;
- mCancelDeferredWithScreenshot = screenshot;
- }
-
- /**
- * @return Whether we should defer the cancel from a root task order change until the next app
- * transition.
- */
- boolean shouldDeferCancelUntilNextTransition() {
- return mRequestDeferCancelUntilNextTransition;
- }
-
- /**
- * @return Whether we should both defer the cancel from a root task order change until the next
- * app transition, and also that the deferred cancel should replace the contents of the leash
- * with a screenshot.
- */
- boolean shouldDeferCancelWithScreenshot() {
- return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot;
- }
-
- private ArrayMap<Task, TaskSnapshot> screenshotRecentTasks() {
- final TaskSnapshotController snapshotController = mService.mTaskSnapshotController;
- final ArrayMap<Task, TaskSnapshot> snapshotMap = new ArrayMap<>();
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
- final Task task = adapter.mTask;
- if (task.isActivityTypeHome()) continue;
- snapshotController.recordSnapshot(task);
- final TaskSnapshot snapshot = snapshotController.getSnapshot(task.mTaskId, task.mUserId,
- false /* restoreFromDisk */, false /* isLowResolution */);
- if (snapshot != null) {
- snapshotMap.put(task, snapshot);
- // Defer until the runner calls back to cleanupScreenshot()
- adapter.setSnapshotOverlay(snapshot);
- }
- }
- snapshotController.addSkipClosingAppSnapshotTasks(snapshotMap.keySet());
- return snapshotMap;
- }
-
- void cleanupAnimation(@ReorderMode int reorderMode) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "cleanupAnimation(): Notify animation finished mPendingAnimations=%d "
- + "reorderMode=%d",
- mPendingAnimations.size(), reorderMode);
- if (reorderMode != REORDER_MOVE_TO_ORIGINAL_POSITION
- && mTargetActivityRecord != mDisplayContent.topRunningActivity()) {
- // Notify the state at the beginning because the removeAnimation may notify the
- // transition is finished. This is a signal that there will be a next transition.
- mDisplayContent.mFixedRotationTransitionListener.notifyRecentsWillBeTop();
- }
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
- if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
- taskAdapter.mTask.dontAnimateDimExit();
- }
- removeAnimation(taskAdapter);
- taskAdapter.onCleanup();
- }
- // Should already be empty, but clean-up pending task-appears in-case they weren't sent.
- mPendingNewTaskTargets.clear();
- mPendingTaskAppears.clear();
-
- for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
- final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i);
- removeWallpaperAnimation(wallpaperAdapter);
- }
-
- restoreNavigationBarFromApp(
- reorderMode == REORDER_MOVE_TO_TOP || mIsAddingTaskToTargets /* animate */);
-
- // Clear any pending failsafe runnables
- mService.mH.removeCallbacks(mFailsafeRunnable);
- mDisplayContent.mAppTransition.unregisterListener(mAppTransitionListener);
-
- // Clear references to the runner
- unlinkToDeathOfRunner();
- mRunner = null;
- mCanceled = true;
-
- // Restore IME icon only when moving the original app task to front from recents, in case
- // IME icon may missing if the moving task has already been the current focused task.
- if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION && !mIsAddingTaskToTargets) {
- InputMethodManagerInternal.get().updateImeWindowStatus(
- false /* disableImeIcon */, mDisplayId);
- }
-
- // Update the input windows after the animation is complete
- final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
- inputMonitor.updateInputWindowsLw(true /*force*/);
-
- // We have deferred all notifications to the target app as a part of the recents animation,
- // so if we are actually transitioning there, notify again here
- if (mTargetActivityRecord != null) {
- if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
- mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(
- mTargetActivityRecord.token);
- }
- }
- mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
-
- // Notify that the animation has ended
- if (mStatusBar != null) {
- mStatusBar.onRecentsAnimationStateChanged(false /* running */);
- }
- }
-
- void scheduleFailsafe() {
- mService.mH.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY);
- }
-
- void onFailsafe() {
- forceCancelAnimation(
- mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
- "onFailsafe");
- }
-
- private void linkToDeathOfRunner() throws RemoteException {
- if (!mLinkedToDeathOfRunner) {
- mRunner.asBinder().linkToDeath(this, 0);
- mLinkedToDeathOfRunner = true;
- }
- }
-
- private void unlinkToDeathOfRunner() {
- if (mLinkedToDeathOfRunner) {
- mRunner.asBinder().unlinkToDeath(this, 0);
- mLinkedToDeathOfRunner = false;
- }
- }
-
- @Override
- public void binderDied() {
- forceCancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied");
-
- synchronized (mService.getWindowManagerLock()) {
- // Clear associated input consumers on runner death
- final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
- final InputConsumerImpl consumer = inputMonitor.getInputConsumer(
- INPUT_CONSUMER_RECENTS_ANIMATION);
- if (consumer != null) {
- inputMonitor.destroyInputConsumer(consumer.mToken);
- }
- }
- }
-
- void checkAnimationReady(WallpaperController wallpaperController) {
- if (mPendingStart) {
- final boolean wallpaperReady = !isTargetOverWallpaper()
- || (wallpaperController.getWallpaperTarget() != null
- && wallpaperController.wallpaperTransitionReady());
- if (wallpaperReady) {
- mService.getRecentsAnimationController().startAnimation();
- }
- }
- }
-
- boolean isWallpaperVisible(WindowState w) {
- return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION &&
- ((w.mActivityRecord != null && mTargetActivityRecord == w.mActivityRecord)
- || isAnimatingTask(w.getTask()))
- && isTargetOverWallpaper() && w.isOnScreen();
- }
-
- /**
- * @return Whether to use the input consumer to override app input to route home/recents.
- */
- boolean shouldApplyInputConsumer(ActivityRecord activity) {
- // Only apply the input consumer if it is enabled, it is not the target (home/recents)
- // being revealed with the transition, and we are actively animating the app as a part of
- // the animation
- return mInputConsumerEnabled && activity != null
- && !isTargetApp(activity) && isAnimatingApp(activity);
- }
-
- boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle) {
- // Update the input consumer touchable region to match the target app main window
- final WindowState targetAppMainWindow = getTargetAppMainWindow();
- if (targetAppMainWindow != null) {
- targetAppMainWindow.getBounds(mTmpRect);
- inputWindowHandle.touchableRegion.set(mTmpRect);
- return true;
- }
- return false;
- }
-
- boolean isTargetApp(ActivityRecord activity) {
- return mTargetActivityRecord != null && activity == mTargetActivityRecord;
- }
-
- private boolean isTargetOverWallpaper() {
- if (mTargetActivityRecord == null) {
- return false;
- }
- return mTargetActivityRecord.windowsCanBeWallpaperTarget();
- }
-
- WindowState getTargetAppMainWindow() {
- if (mTargetActivityRecord == null) {
- return null;
- }
- return mTargetActivityRecord.findMainWindow();
- }
-
- DisplayArea getTargetAppDisplayArea() {
- if (mTargetActivityRecord == null) {
- return null;
- }
- return mTargetActivityRecord.getDisplayArea();
- }
-
- boolean isAnimatingTask(Task task) {
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- if (task == mPendingAnimations.get(i).mTask) {
- return true;
- }
- }
- return false;
- }
-
- boolean isAnimatingWallpaper(WallpaperWindowToken token) {
- for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
- if (token == mPendingWallpaperAnimations.get(i).getToken()) {
- return true;
- }
- }
- return false;
- }
-
- private boolean isAnimatingApp(ActivityRecord activity) {
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- if (activity.isDescendantOf(mPendingAnimations.get(i).mTask)) {
- return true;
- }
- }
- return false;
- }
-
- boolean shouldIgnoreForAccessibility(WindowState windowState) {
- final Task task = windowState.getTask();
- return task != null && isAnimatingTask(task) && !isTargetApp(windowState.mActivityRecord);
- }
-
- /**
- * If the animation target ActivityRecord has a fixed rotation ({@link
- * WindowToken#hasFixedRotationTransform()}, the provided wallpaper will be rotated accordingly.
- *
- * This avoids any screen rotation animation when animating to the Recents view.
- */
- void linkFixedRotationTransformIfNeeded(@NonNull WindowToken wallpaper) {
- if (mTargetActivityRecord == null) {
- return;
- }
- wallpaper.linkFixedRotationTransform(mTargetActivityRecord);
- }
-
- @VisibleForTesting
- class TaskAnimationAdapter implements AnimationAdapter {
-
- private final Task mTask;
- private SurfaceControl mCapturedLeash;
- private OnAnimationFinishedCallback mCapturedFinishCallback;
- private @AnimationType int mLastAnimationType;
- private final boolean mIsRecentTaskInvisible;
- private RemoteAnimationTarget mTarget;
- private final Rect mBounds = new Rect();
- // The bounds of the target relative to its parent.
- private final Rect mLocalBounds = new Rect();
- // The final surface transaction when animation is finished.
- private PictureInPictureSurfaceTransaction mFinishTransaction;
- // An overlay used to mask the content as an app goes into PIP
- private SurfaceControl mFinishOverlay;
- // An overlay used for canceling the animation with a screenshot
- private SurfaceControl mSnapshotOverlay;
-
- TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) {
- mTask = task;
- mIsRecentTaskInvisible = isRecentTaskInvisible;
- mBounds.set(mTask.getBounds());
-
- mLocalBounds.set(mBounds);
- Point tmpPos = new Point();
- mTask.getRelativePosition(tmpPos);
- mLocalBounds.offsetTo(tmpPos.x, tmpPos.y);
- }
-
- /**
- * @param overrideTaskId overrides the target's taskId. It may differ from mTaskId and thus
- * can differ from taskInfo. This mismatch is needed, however, in
- * some cases where we are animating root tasks but need need leaf
- * ids for identification. If this is INVALID (-1), then mTaskId
- * will be used.
- * @param overrideMode overrides the target's mode. If this is -1, the mode will be
- * calculated relative to going to the target activity (ie. OPENING if
- * this is the target task, CLOSING otherwise).
- */
- RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId, int overrideMode) {
- ActivityRecord topApp = mTask.getTopRealVisibleActivity();
- if (topApp == null) {
- topApp = mTask.getTopVisibleActivity();
- }
- final WindowState mainWindow = topApp != null
- ? topApp.findMainWindow()
- : null;
- if (mainWindow == null) {
- return null;
- }
- final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
- mBounds, Type.systemBars(), false /* ignoreVisibility */).toRect();
- InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
- final int mode = overrideMode != MODE_UNKNOWN
- ? overrideMode
- : topApp.getActivityType() == mTargetActivityType
- ? MODE_OPENING
- : MODE_CLOSING;
- if (overrideTaskId < 0) {
- overrideTaskId = mTask.mTaskId;
- }
- mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash,
- !topApp.fillsParent(), new Rect(),
- insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top),
- mLocalBounds, mBounds, mTask.getWindowConfiguration(),
- mIsRecentTaskInvisible, null, null, mTask.getTaskInfo(),
- topApp.checkEnterPictureInPictureAppOpsState());
-
- final ActivityRecord topActivity = mTask.getTopNonFinishingActivity();
- if (topActivity != null && topActivity.mStartingData != null
- && topActivity.mStartingData.hasImeSurface()) {
- mTarget.setWillShowImeOnTarget(true);
- }
- return mTarget;
- }
-
- void setSnapshotOverlay(TaskSnapshot snapshot) {
- // Create a surface control for the snapshot and reparent it to the leash
- final HardwareBuffer buffer = snapshot.getHardwareBuffer();
- if (buffer == null) {
- return;
- }
-
- final SurfaceSession session = new SurfaceSession();
- mSnapshotOverlay = mService.mSurfaceControlFactory.apply(session)
- .setName("RecentTaskScreenshotSurface")
- .setCallsite("TaskAnimationAdapter.setSnapshotOverlay")
- .setFormat(buffer.getFormat())
- .setParent(mCapturedLeash)
- .setBLASTLayer()
- .build();
-
- final float scale = 1.0f * mTask.getBounds().width() / buffer.getWidth();
- mTask.getPendingTransaction()
- .setBuffer(mSnapshotOverlay, GraphicBuffer.createFromHardwareBuffer(buffer))
- .setColorSpace(mSnapshotOverlay, snapshot.getColorSpace())
- .setLayer(mSnapshotOverlay, Integer.MAX_VALUE)
- .setMatrix(mSnapshotOverlay, scale, 0, 0, scale)
- .show(mSnapshotOverlay)
- .apply();
- }
-
- void onRemove() {
- if (mSnapshotOverlay != null) {
- // Clean up the snapshot overlay if necessary
- mTask.getPendingTransaction()
- .remove(mSnapshotOverlay)
- .apply();
- mSnapshotOverlay = null;
- }
- mTask.setCanAffectSystemUiFlags(true);
- mCapturedFinishCallback.onAnimationFinished(mLastAnimationType, this);
- }
-
- void onCleanup() {
- final Transaction pendingTransaction = mTask.getPendingTransaction();
- if (mFinishTransaction != null) {
- // Reparent the overlay
- if (mFinishOverlay != null) {
- pendingTransaction.reparent(mFinishOverlay, mTask.mSurfaceControl);
- }
-
- // Transfer the transform from the leash to the task
- PictureInPictureSurfaceTransaction.apply(mFinishTransaction,
- mTask.mSurfaceControl, pendingTransaction);
- mTask.setLastRecentsAnimationTransaction(mFinishTransaction, mFinishOverlay);
- if (mDisplayContent.isFixedRotationLaunchingApp(mTargetActivityRecord)) {
- // The transaction is needed for position when rotating the display.
- mDisplayContent.mPinnedTaskController.setEnterPipTransaction(
- mFinishTransaction);
- }
- // In the case where we are transferring the transform to the task in preparation
- // for entering PIP, we disable the task being able to affect sysui flags otherwise
- // it may cause a flash
- if (mTask.getActivityType() != mTargetActivityType
- && mFinishTransaction.getShouldDisableCanAffectSystemUiFlags()) {
- mTask.setCanAffectSystemUiFlags(false);
- }
- mFinishTransaction = null;
- mFinishOverlay = null;
- pendingTransaction.apply();
- } else if (!mTask.isAttached()) {
- // Apply the task's pending transaction in case it is detached and its transaction
- // is not reachable.
- pendingTransaction.apply();
- }
- }
-
- @VisibleForTesting
- public SurfaceControl getSnapshotOverlay() {
- return mSnapshotOverlay;
- }
-
- @Override
- public boolean getShowWallpaper() {
- return false;
- }
-
- @Override
- public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
- // Restore position and root task crop until client has a chance to modify it.
- t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top);
- mTmpRect.set(mLocalBounds);
- mTmpRect.offsetTo(0, 0);
- t.setWindowCrop(animationLeash, mTmpRect);
- mCapturedLeash = animationLeash;
- mCapturedFinishCallback = finishCallback;
- mLastAnimationType = type;
- }
-
- @Override
- public void onAnimationCancelled(SurfaceControl animationLeash) {
- // Cancel the animation immediately if any single task animator is canceled
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled");
- }
-
- @Override
- public long getDurationHint() {
- return 0;
- }
-
- @Override
- public long getStatusBarTransitionsStartTime() {
- return SystemClock.uptimeMillis();
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.println("task=" + mTask);
- if (mTarget != null) {
- pw.print(prefix); pw.println("Target:");
- mTarget.dump(pw, prefix + " ");
- } else {
- pw.print(prefix); pw.println("Target: null");
- }
- pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
- pw.println("mLocalBounds=" + mLocalBounds);
- pw.println("mFinishTransaction=" + mFinishTransaction);
- pw.println("mBounds=" + mBounds);
- pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
- }
-
- @Override
- public void dumpDebug(ProtoOutputStream proto) {
- final long token = proto.start(REMOTE);
- if (mTarget != null) {
- mTarget.dumpDebug(proto, TARGET);
- }
- proto.end(token);
- }
- }
-
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":");
- pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart);
- pw.print(innerPrefix); pw.println("mPendingAnimations=" + mPendingAnimations.size());
- pw.print(innerPrefix); pw.println("mCanceled=" + mCanceled);
- pw.print(innerPrefix); pw.println("mInputConsumerEnabled=" + mInputConsumerEnabled);
- pw.print(innerPrefix); pw.println("mTargetActivityRecord=" + mTargetActivityRecord);
- pw.print(innerPrefix); pw.println("isTargetOverWallpaper=" + isTargetOverWallpaper());
- pw.print(innerPrefix); pw.println("mRequestDeferCancelUntilNextTransition="
- + mRequestDeferCancelUntilNextTransition);
- pw.print(innerPrefix); pw.println("mCancelOnNextTransitionStart="
- + mCancelOnNextTransitionStart);
- pw.print(innerPrefix); pw.println("mCancelDeferredWithScreenshot="
- + mCancelDeferredWithScreenshot);
- pw.print(innerPrefix); pw.println("mPendingCancelWithScreenshotReorderMode="
- + mPendingCancelWithScreenshotReorderMode);
- }
-}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 862f84dbd73d..a6d965955272 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -804,12 +804,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
checkAppTransitionReady(surfacePlacer);
- // Defer starting the recents animation until the wallpaper has drawn
- final RecentsAnimationController recentsAnimationController =
- mWmService.getRecentsAnimationController();
- if (recentsAnimationController != null) {
- recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
- }
mWmService.mAtmService.mBackNavigationController
.checkAnimationReady(defaultDisplay.mWallpaperController);
@@ -1471,9 +1465,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// Updates the extra information of the intent.
if (fromHomeKey) {
homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
- if (mWindowManager.getRecentsAnimationController() != null) {
- mWindowManager.getRecentsAnimationController().cancelAnimationForHomeStart();
- }
}
homeIntent.putExtra(WindowManagerPolicy.EXTRA_START_REASON, reason);
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9cfd39688c12..57f9be097ee6 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -32,8 +32,8 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -585,7 +585,6 @@ public class SurfaceAnimator {
ANIMATION_TYPE_APP_TRANSITION,
ANIMATION_TYPE_SCREEN_ROTATION,
ANIMATION_TYPE_DIMMER,
- ANIMATION_TYPE_RECENTS,
ANIMATION_TYPE_WINDOW_ANIMATION,
ANIMATION_TYPE_INSETS_CONTROL,
ANIMATION_TYPE_TOKEN_TRANSFORM,
@@ -604,7 +603,6 @@ public class SurfaceAnimator {
case ANIMATION_TYPE_APP_TRANSITION: return "app_transition";
case ANIMATION_TYPE_SCREEN_ROTATION: return "screen_rotation";
case ANIMATION_TYPE_DIMMER: return "dimmer";
- case ANIMATION_TYPE_RECENTS: return "recents_animation";
case ANIMATION_TYPE_WINDOW_ANIMATION: return "window_animation";
case ANIMATION_TYPE_INSETS_CONTROL: return "insets_animation";
case ANIMATION_TYPE_TOKEN_TRANSFORM: return "token_transform";
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 13d6ed5e69c4..21be0fc2ac68 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -61,7 +61,6 @@ import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -95,7 +94,6 @@ import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.TaskProto.AFFINITY;
import static com.android.server.wm.TaskProto.BOUNDS;
import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
@@ -168,7 +166,6 @@ import android.view.InsetsState;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.view.WindowManager;
-import android.view.WindowManager.TransitionOldType;
import android.window.ITaskOrganizer;
import android.window.PictureInPictureSurfaceTransaction;
import android.window.StartingWindowInfo;
@@ -2957,14 +2954,6 @@ class Task extends TaskFragment {
if (isOrganized()) {
return false;
}
- // Don't animate while the task runs recents animation but only if we are in the mode
- // where we cancel with deferred screenshot, which means that the controller has
- // transformed the task.
- final RecentsAnimationController controller = mWmService.getRecentsAnimationController();
- if (controller != null && controller.isAnimatingTask(this)
- && controller.shouldDeferCancelUntilNextTransition()) {
- return false;
- }
return true;
}
@@ -2976,8 +2965,7 @@ class Task extends TaskFragment {
/** Checking if self or its child tasks are animated by recents animation. */
boolean isAnimatingByRecents() {
- return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
- || mTransitionController.isTransientHide(this);
+ return mTransitionController.isTransientHide(this);
}
WindowState getTopVisibleAppMainWindow() {
@@ -3282,30 +3270,6 @@ class Task extends TaskFragment {
}
@Override
- protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
- @TransitionOldType int transit, boolean isVoiceInteraction,
- @Nullable ArrayList<WindowContainer> sources) {
- final RecentsAnimationController control = mWmService.getRecentsAnimationController();
- if (control != null) {
- // We let the transition to be controlled by RecentsAnimation, and callback task's
- // RemoteAnimationTarget for remote runner to animate.
- if (enter && !isActivityTypeHomeOrRecents()) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "applyAnimationUnchecked, control: %s, task: %s, transit: %s",
- control, asTask(), AppTransition.appTransitionOldToString(transit));
- final int size = sources != null ? sources.size() : 0;
- control.addTaskToTargets(this, (type, anim) -> {
- for (int i = 0; i < size; ++i) {
- sources.get(i).onAnimationFinished(type, anim);
- }
- });
- }
- } else {
- super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
- }
- }
-
- @Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
super.dump(pw, prefix, dumpAll);
mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
@@ -6230,26 +6194,6 @@ class Task extends TaskFragment {
ActivityOptions.abort(options);
}
- boolean shouldSleepActivities() {
- final DisplayContent display = mDisplayContent;
- final boolean isKeyguardGoingAway = (mDisplayContent != null)
- ? mDisplayContent.isKeyguardGoingAway()
- : mRootWindowContainer.getDefaultDisplay().isKeyguardGoingAway();
-
- // Do not sleep activities in this root task if we're marked as focused and the keyguard
- // is in the process of going away.
- if (isKeyguardGoingAway && isFocusedRootTaskOnDisplay()
- // Avoid resuming activities on secondary displays since we don't want bubble
- // activities to be resumed while bubble is still collapsed.
- // TODO(b/113840485): Having keyguard going away state for secondary displays.
- && display != null
- && display.isDefaultDisplay) {
- return false;
- }
-
- return display != null ? display.isSleeping() : mAtmService.isSleepingLocked();
- }
-
private Rect getRawBounds() {
return super.getBounds();
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 01fea479e6da..638e92f112c7 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -143,13 +143,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
* current focused root task.
*/
Task mLastFocusedRootTask;
- /**
- * All of the root tasks on this display. Order matters, topmost root task is in front of all
- * other root tasks, bottommost behind. Accessed directly by ActivityManager package classes.
- * Any calls changing the list should also call {@link #onRootTaskOrderChanged(Task)}.
- */
- private ArrayList<OnRootTaskOrderChangedListener> mRootTaskOrderChangedCallbacks =
- new ArrayList<>();
/**
* The task display area is removed from the system and we are just waiting for all activities
@@ -332,7 +325,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("addChildTask");
mAtmService.updateSleepIfNeededLocked();
- onRootTaskOrderChanged(task);
}
@Override
@@ -424,10 +416,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// Update the top resumed activity because the preferred top focusable task may be changed.
mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("positionChildTaskAt");
-
- if (mChildren.indexOf(child) != oldPosition) {
- onRootTaskOrderChanged(child);
- }
}
void onLeafTaskRemoved(int taskId) {
@@ -844,7 +832,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
mLaunchAdjacentFlagRootTask = null;
}
mDisplayContent.releaseSelfIfNeeded();
- onRootTaskOrderChanged(rootTask);
}
/**
@@ -1743,35 +1730,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
return mRemoved;
}
- /**
- * Adds a listener to be notified whenever the root task order in the display changes. Currently
- * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
- * current animation when the system state changes.
- */
- void registerRootTaskOrderChangedListener(OnRootTaskOrderChangedListener listener) {
- if (!mRootTaskOrderChangedCallbacks.contains(listener)) {
- mRootTaskOrderChangedCallbacks.add(listener);
- }
- }
-
- /**
- * Removes a previously registered root task order change listener.
- */
- void unregisterRootTaskOrderChangedListener(OnRootTaskOrderChangedListener listener) {
- mRootTaskOrderChangedCallbacks.remove(listener);
- }
-
- /**
- * Notifies of a root task order change
- *
- * @param rootTask The root task which triggered the order change
- */
- void onRootTaskOrderChanged(Task rootTask) {
- for (int i = mRootTaskOrderChangedCallbacks.size() - 1; i >= 0; i--) {
- mRootTaskOrderChangedCallbacks.get(i).onRootTaskOrderChanged(rootTask);
- }
- }
-
@Override
boolean canCreateRemoteAnimationTarget() {
// In the legacy transition system, promoting animation target from TaskFragment to
@@ -1786,13 +1744,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
return mDisplayContent.isHomeSupported() && mCanHostHomeTask;
}
- /**
- * Callback for when the order of the root tasks in the display changes.
- */
- interface OnRootTaskOrderChangedListener {
- void onRootTaskOrderChanged(Task rootTask);
- }
-
void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
try {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c8139faab4c4..f58b322cab36 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2145,8 +2145,22 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
boolean shouldSleepActivities() {
- final Task task = getRootTask();
- return task != null && task.shouldSleepActivities();
+ final DisplayContent dc = mDisplayContent;
+ if (dc == null) {
+ return mAtmService.isSleepingLocked();
+ }
+ if (!dc.isSleeping()) {
+ return false;
+ }
+ // In case the unlocking order is keyguard-going-away -> screen-turning-on (display is
+ // sleeping by screen-off-token which may be notified to release from power manager's
+ // thread), keep the activities resume-able to avoid extra activity lifecycle when
+ // performing keyguard-going-away. This only applies to default display because currently
+ // the per-display keyguard-going-away state is assigned from a global signal.
+ if (!dc.isDefaultDisplay || !dc.isKeyguardGoingAway()) {
+ return true;
+ }
+ return !shouldBeVisible(null /* starting */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e76e94d58800..82ede7efda5a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -181,7 +181,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final @TransitionType int mType;
private int mSyncId = -1;
private @TransitionFlags int mFlags;
- private final TransitionController mController;
+ final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
private final Token mToken;
@@ -329,6 +329,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
*/
ArrayList<ActivityRecord> mConfigAtEndActivities = null;
+ /** The current head of the chain of actions related to this transition. */
+ ActionChain mChainHead = null;
+
@VisibleForTesting
Transition(@TransitionType int type, @TransitionFlags int flags,
TransitionController controller, BLASTSyncEngine syncEngine) {
@@ -1207,10 +1210,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
* The transition has finished animating and is ready to finalize WM state. This should not
* be called directly; use {@link TransitionController#finishTransition} instead.
*/
- void finishTransition() {
+ void finishTransition(@NonNull ActionChain chain) {
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
asyncTraceEnd(System.identityHashCode(this));
}
+ if (!chain.isFinishing()) {
+ throw new IllegalStateException("Can't finish on a non-finishing transition "
+ + chain.mTransition);
+ }
mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
mController.mLoggerHandler.post(mLogger::logOnFinish);
mController.mTransitionTracer.logFinishedTransition(this);
@@ -1453,7 +1460,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Clean up input monitors (for recents)
final DisplayContent dc =
mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
- dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
+ dc.getInputMonitor().setActiveRecents(null /* task */, null /* layer */);
dc.getInputMonitor().updateInputWindowsLw(false /* force */);
}
if (mTransientLaunches != null) {
@@ -2163,7 +2170,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (mFinishTransaction != null) {
mFinishTransaction.apply();
}
- mController.finishTransition(this);
+ mController.finishTransition(mController.mAtm.mChainTracker.startFinish("clean-up", this));
}
private void cleanUpInternal() {
@@ -2249,7 +2256,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Recents has an input-consumer to grab input from the "live tile" app. Set that up here
final InputConsumerImpl recentsAnimationInputConsumer =
dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
- ActivityRecord recentsActivity = null;
+ Task recentsTask = null;
if (recentsAnimationInputConsumer != null) {
// Find the top-most going-away task and the recents activity. The top-most
// is used as layer reference while the recents is used for registering the consumer
@@ -2264,20 +2271,20 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final int activityType = taskInfo.topActivityType;
final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
|| activityType == ACTIVITY_TYPE_RECENTS;
- if (isRecents && recentsActivity == null) {
- recentsActivity = task.getTopVisibleActivity();
+ if (isRecents && recentsTask == null) {
+ recentsTask = task;
} else if (!isRecents && topNonRecentsTask == null) {
topNonRecentsTask = task;
}
}
- if (recentsActivity != null && topNonRecentsTask != null) {
+ if (recentsTask != null && topNonRecentsTask != null) {
recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
topNonRecentsTask.getBounds());
- dc.getInputMonitor().setActiveRecents(recentsActivity, topNonRecentsTask);
+ dc.getInputMonitor().setActiveRecents(recentsTask, topNonRecentsTask);
}
}
- if (recentsActivity == null) {
+ if (recentsTask == null) {
// No recents activity on `dc`, its probably on a different display.
return;
}
@@ -3379,6 +3386,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return false;
}
+ void recordChain(@NonNull ActionChain chain) {
+ chain.mPrevious = mChainHead;
+ mChainHead = chain;
+ }
+
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 1d2b693995c8..56a24dd4ca49 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -921,7 +921,12 @@ class TransitionController {
}
/** @see Transition#finishTransition */
- void finishTransition(Transition record) {
+ void finishTransition(@NonNull ActionChain chain) {
+ if (!chain.isFinishing()) {
+ throw new IllegalStateException("Can't finish on a non-finishing transition "
+ + chain.mTransition);
+ }
+ final Transition record = chain.mTransition;
// It is usually a no-op but make sure that the metric consumer is removed.
mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */);
// It is a no-op if the transition did not change the display.
@@ -937,7 +942,7 @@ class TransitionController {
mTrackCount = 0;
}
updateRunningRemoteAnimation(record, false /* isPlaying */);
- record.finishTransition();
+ record.finishTransition(chain);
for (int i = mAnimatingExitWindows.size() - 1; i >= 0; i--) {
final WindowState w = mAnimatingExitWindows.get(i);
if (w.mAnimatingExit && w.mHasSurface && !w.inTransition()) {
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 016b65ed06d8..f1941afe8f58 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -201,8 +201,10 @@ class TransparentPolicy {
// never has letterbox.
return true;
}
+ final AppCompatSizeCompatModePolicy scmPolicy = mActivityRecord.mAppCompatController
+ .getAppCompatSizeCompatModePolicy();
if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
- || mActivityRecord.hasAppCompatDisplayInsetsWithoutInheritance()) {
+ || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) {
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 4536f244215c..06010bb1642e 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -172,8 +172,8 @@ class WallpaperController {
&& animatingContainer.getAnimation() != null
&& animatingContainer.getAnimation().getShowWallpaper();
final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
- if (isRecentsTransitionTarget(w) || isBackNavigationTarget(w)) {
- if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w);
+ if (isBackNavigationTarget(w)) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Found back animation wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
return true;
} else if (hasWallpaper
@@ -199,15 +199,6 @@ class WallpaperController {
return false;
};
- private boolean isRecentsTransitionTarget(WindowState w) {
- if (w.mTransitionController.isShellTransitionsEnabled()) {
- return false;
- }
- // The window is either the recents activity or is in the task animating by the recents.
- final RecentsAnimationController controller = mService.getRecentsAnimationController();
- return controller != null && controller.isWallpaperVisible(w);
- }
-
private boolean isBackNavigationTarget(WindowState w) {
// The window is in animating by back navigation and set to show wallpaper.
return mService.mAtmService.mBackNavigationController.isWallpaperVisible(w);
@@ -928,12 +919,6 @@ class WallpaperController {
Slog.v(TAG, "*** WALLPAPER DRAW TIMEOUT");
}
- // If there was a pending recents animation, start the animation anyways (it's better
- // to not see the wallpaper than for the animation to not start)
- if (mService.getRecentsAnimationController() != null) {
- mService.getRecentsAnimationController().startAnimation();
- }
-
// If there was a pending back navigation animation that would show wallpaper, start
// the animation due to it was skipped in previous surface placement.
mService.mAtmService.mBackNavigationController.startAnimation();
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 384d111eb058..89ad5640ef83 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -161,15 +161,7 @@ class WallpaperWindowToken extends WindowToken {
mDisplayContent.mWallpaperController.getWallpaperTarget();
if (visible && wallpaperTarget != null) {
- final RecentsAnimationController recentsAnimationController =
- mWmService.getRecentsAnimationController();
- if (recentsAnimationController != null
- && recentsAnimationController.isAnimatingTask(wallpaperTarget.getTask())) {
- // If the Recents animation is running, and the wallpaper target is the animating
- // task we want the wallpaper to be rotated in the same orientation as the
- // RecentsAnimation's target (e.g the launcher)
- recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
- } else if ((wallpaperTarget.mActivityRecord == null
+ if ((wallpaperTarget.mActivityRecord == null
// Ignore invisible activity because it may be moving to background.
|| wallpaperTarget.mActivityRecord.isVisibleRequested())
&& wallpaperTarget.mToken.hasFixedRotationTransform()) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 03342d316d1c..13334a5f29b1 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -19,7 +19,6 @@ package com.android.server.wm;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -218,8 +217,8 @@ public class WindowAnimator {
private void updateRunningExpensiveAnimationsLegacy() {
final boolean runningExpensiveAnimations =
mService.mRoot.isAnimating(TRANSITION | CHILDREN /* flags */,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION
- | ANIMATION_TYPE_RECENTS /* typesToCheck */);
+ ANIMATION_TYPE_APP_TRANSITION
+ | ANIMATION_TYPE_SCREEN_ROTATION /* typesToCheck */);
if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
mService.mSnapshotController.setPause(true);
mTransaction.setEarlyWakeupStart();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a980b7794547..6995027aac78 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -51,7 +51,6 @@ import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -1242,8 +1241,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
boolean inTransitionSelfOrParent() {
if (!mTransitionController.isShellTransitionsEnabled()) {
- return isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+ return isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION);
}
return inTransition();
}
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index 57fc4c7a6860..80f3c44267d7 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -20,7 +20,7 @@ import static android.view.SurfaceControl.METADATA_OWNER_UID;
import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH;
@@ -118,14 +118,7 @@ class WindowContainerThumbnail implements Animatable {
mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(),
mWindowContainer.getDisplayContent().getWindowCornerRadius()),
mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */,
- ANIMATION_TYPE_RECENTS);
- }
-
- /**
- * Start animation with existing adapter.
- */
- void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
- mSurfaceAnimator.startAnimation(t, anim, hidden, ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION);
}
private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d8df645bf076..74931781e752 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -129,7 +129,6 @@ import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_R
import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -246,7 +245,6 @@ import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.TypedValue;
@@ -266,7 +264,6 @@ import android.view.IDisplayWindowListener;
import android.view.IInputFilter;
import android.view.IOnKeyguardExitResult;
import android.view.IPinnedTaskListener;
-import android.view.IRecentsAnimationRunner;
import android.view.IRotationWatcher;
import android.view.IScrollCaptureResponseListener;
import android.view.ISystemGestureExclusionListener;
@@ -681,7 +678,6 @@ public class WindowManagerService extends IWindowManager.Stub
private final SparseIntArray mOrientationMapping = new SparseIntArray();
final AccessibilityController mAccessibilityController;
- private RecentsAnimationController mRecentsAnimationController;
Watermark mWatermark;
StrictModeFlash mStrictModeFlash;
@@ -1159,17 +1155,12 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
- // While running a recents animation, this will get called early because we show the
- // recents animation target activity immediately when the animation starts. Defer the
- // mLaunchTaskBehind updates until recents animation finishes.
- if (atoken.mLaunchTaskBehind && !isRecentsAnimationTarget(atoken)) {
+ if (atoken.mLaunchTaskBehind) {
mAtmService.mTaskSupervisor.scheduleLaunchTaskBehindComplete(atoken.token);
atoken.mLaunchTaskBehind = false;
} else {
atoken.updateReportedVisibilityLocked();
- // We should also defer sending the finished callback until the recents animation
- // successfully finishes.
- if (atoken.mEnteringAnimation && !isRecentsAnimationTarget(atoken)) {
+ if (atoken.mEnteringAnimation) {
atoken.mEnteringAnimation = false;
if (atoken.attachedToProcess()) {
try {
@@ -2733,8 +2724,7 @@ public class WindowManagerService extends IWindowManager.Stub
win.mTransitionController.mAnimatingExitWindows.add(win);
reason = "inTransition";
}
- } else if (win.isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+ } else if (win.isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
// Already animating as part of a legacy app-transition.
reason = "inLegacyTransition";
}
@@ -3168,7 +3158,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
// TODO(multi-display): remove when no default display use case.
- // (i.e. KeyguardController / RecentsAnimation)
+ // (i.e. KeyguardController)
public void executeAppTransition() {
if (!checkCallingPermission(MANAGE_APP_TOKENS, "executeAppTransition()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3176,57 +3166,6 @@ public class WindowManagerService extends IWindowManager.Stub
getDefaultDisplayContentLocked().executeAppTransition();
}
- void initializeRecentsAnimation(int targetActivityType,
- IRecentsAnimationRunner recentsAnimationRunner,
- RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId,
- SparseBooleanArray recentTaskIds, ActivityRecord targetActivity) {
- mRecentsAnimationController = new RecentsAnimationController(this, recentsAnimationRunner,
- callbacks, displayId);
- mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
- mRecentsAnimationController.initialize(targetActivityType, recentTaskIds, targetActivity);
- }
-
- @VisibleForTesting
- void setRecentsAnimationController(RecentsAnimationController controller) {
- mRecentsAnimationController = controller;
- }
-
- RecentsAnimationController getRecentsAnimationController() {
- return mRecentsAnimationController;
- }
-
- void cancelRecentsAnimation(
- @RecentsAnimationController.ReorderMode int reorderMode, String reason) {
- if (mRecentsAnimationController != null) {
- // This call will call through to cleanupAnimation() below after the animation is
- // canceled
- mRecentsAnimationController.cancelAnimation(reorderMode, reason);
- }
- }
-
-
- void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
- if (mRecentsAnimationController != null) {
- final RecentsAnimationController controller = mRecentsAnimationController;
- mRecentsAnimationController = null;
- controller.cleanupAnimation(reorderMode);
- // TODO(multi-display): currently only default display support recents animation.
- final DisplayContent dc = getDefaultDisplayContentLocked();
- if (dc.mAppTransition.isTransitionSet()) {
- dc.mSkipAppTransitionAnimation = true;
- }
- dc.forAllWindowContainers((wc) -> {
- if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
- wc.cancelAnimation();
- }
- });
- }
- }
-
- boolean isRecentsAnimationTarget(ActivityRecord r) {
- return mRecentsAnimationController != null && mRecentsAnimationController.isTargetApp(r);
- }
-
boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio(
aspectRatio);
@@ -3258,11 +3197,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void triggerAnimationFailsafe() {
- mH.sendEmptyMessage(H.ANIMATION_FAILSAFE);
- }
-
- @Override
public void onKeyguardShowingAndNotOccludedChanged() {
mH.sendEmptyMessage(H.RECOMPUTE_FOCUS);
dispatchKeyguardLockedState();
@@ -5652,7 +5586,6 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int UPDATE_ANIMATION_SCALE = 51;
public static final int WINDOW_HIDE_TIMEOUT = 52;
public static final int SET_HAS_OVERLAY_UI = 58;
- public static final int ANIMATION_FAILSAFE = 60;
public static final int RECOMPUTE_FOCUS = 61;
public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
public static final int WINDOW_STATE_BLAST_SYNC_TIMEOUT = 64;
@@ -5887,14 +5820,6 @@ public class WindowManagerService extends IWindowManager.Stub
mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
break;
}
- case ANIMATION_FAILSAFE: {
- synchronized (mGlobalLock) {
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.scheduleFailsafe();
- }
- }
- break;
- }
case RECOMPUTE_FOCUS: {
synchronized (mGlobalLock) {
updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
@@ -7036,10 +6961,6 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" window="); pw.print(mWindowAnimationScaleSetting);
pw.print(" transition="); pw.print(mTransitionAnimationScaleSetting);
pw.print(" animator="); pw.println(mAnimatorDurationScaleSetting);
- if (mRecentsAnimationController != null) {
- pw.print(" mRecentsAnimationController="); pw.println(mRecentsAnimationController);
- mRecentsAnimationController.dump(pw, " ");
- }
}
}
@@ -9024,6 +8945,22 @@ public class WindowManagerService extends IWindowManager.Stub
// display it's on to the top since that window won't be able to get focus anyway.
return;
}
+
+ final ActivityRecord touchedApp = t.getActivityRecord();
+ if (touchedApp != null && touchedApp.getTask() != null) {
+ final ActivityRecord top = touchedApp.getTask().topRunningActivity();
+ if (top != null && top != touchedApp && top.getTaskFragment().getBounds().contains(
+ touchedApp.getTaskFragment().getBounds())) {
+ // This is a special case where the pointer-down-outside focus on an Activity that's
+ // entirely occluded by the task top running activity, this is possible if the
+ // pointer-down-outside-focus event is delayed (after new activity started on top).
+ // In that case, drop the event to prevent changing focus to a background activity.
+ Slog.w(TAG, "onPointerDownOutsideFocusLocked, drop event because " + touchedApp
+ + " is occluded and should not be focused.");
+ return;
+ }
+ }
+
clearPointerDownOutsideFocusRunnable();
if (shouldDelayTouchOutside(t)) {
@@ -9075,17 +9012,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
clearPointerDownOutsideFocusRunnable();
- if (mRecentsAnimationController != null
- && mRecentsAnimationController.getTargetAppMainWindow() == t) {
- // If there is an active recents animation and touched window is the target,
- // then ignore the touch. The target already handles touches using its own
- // input monitor and we don't want to trigger any lifecycle changes from
- // focusing another window.
- // TODO(b/186770026): We should remove this once we support multiple resumed
- // activities while in overview
- return;
- }
-
final WindowState w = t.getWindowState();
if (w != null) {
final Task task = w.getTask();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e1e64ee10245..60ccdc72ee09 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -223,7 +223,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- applyTransaction(t, -1 /*syncId*/, null /*transition*/, caller);
+ final ActionChain chain = mService.mChainTracker.startLegacy("applyTransactLegacy");
+ applyTransaction(t, -1 /*syncId*/, chain, caller);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -242,7 +243,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
try {
synchronized (mGlobalLock) {
if (callback == null) {
- applyTransaction(t, -1 /* syncId*/, null /*transition*/, caller);
+ final ActionChain chain = mService.mChainTracker.startLegacy("applySyncLegacy");
+ applyTransaction(t, -1 /* syncId*/, chain, caller);
return -1;
}
@@ -262,13 +264,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final int syncId = syncGroup.mSyncId;
if (mTransitionController.isShellTransitionsEnabled()) {
mTransitionController.startLegacySyncOrQueue(syncGroup, (deferred) -> {
- applyTransaction(t, syncId, null /* transition */, caller, deferred);
+ applyTransaction(t, syncId, mService.mChainTracker.startLegacy(
+ "applySyncLegacy"), caller, deferred);
setSyncReady(syncId);
});
} else {
if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup);
- applyTransaction(t, syncId, null /*transition*/, caller);
+ applyTransaction(t, syncId, mService.mChainTracker.startLegacy(
+ "applySyncLegacy"), caller);
setSyncReady(syncId);
} else {
// Because the BLAST engine only supports one sync at a time, queue the
@@ -276,7 +280,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.mWindowManager.mSyncEngine.queueSyncSet(
() -> mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup),
() -> {
- applyTransaction(t, syncId, null /*transition*/, caller);
+ applyTransaction(t, syncId, mService.mChainTracker.startLegacy(
+ "applySyncLegacy"), caller);
setSyncReady(syncId);
});
}
@@ -313,7 +318,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
throw new IllegalArgumentException("Can't use legacy transitions in"
+ " compatibility mode with no WCT.");
}
- applyTransaction(t, -1 /* syncId */, null, caller);
+ applyTransaction(t, -1 /* syncId */,
+ mService.mChainTracker.startLegacy("wrongLegacyTransit"), caller);
return null;
}
final WindowContainerTransaction wct =
@@ -334,10 +340,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
nextTransition.calcParallelCollectType(wct);
mTransitionController.startCollectOrQueue(nextTransition,
(deferred) -> {
+ final ActionChain chain = mService.mChainTracker.start(
+ "startNewTransit", nextTransition);
nextTransition.start();
nextTransition.mLogger.mStartWCT = wct;
- applyTransaction(wct, -1 /* syncId */, nextTransition, caller,
- deferred);
+ applyTransaction(wct, -1 /* syncId */, chain, caller, deferred);
wctApplied.meet();
if (needsSetReady) {
setAllReadyIfNeeded(nextTransition, wct);
@@ -351,7 +358,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
+ " means Shell took too long to respond to a request. WM State may be"
+ " incorrect now, please file a bug");
- applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
+ final ActionChain chain = mService.mChainTracker.startFailsafe("startTransit");
+ chain.mTransition = null;
+ applyTransaction(wct, -1 /*syncId*/, chain, caller);
return transition.getToken();
}
// Currently, application of wct can span multiple looper loops (ie.
@@ -367,16 +376,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (transition.shouldApplyOnDisplayThread()) {
mService.mH.post(() -> {
synchronized (mService.mGlobalLock) {
+ final ActionChain chain = mService.mChainTracker.start(
+ "startTransit", transition);
transition.start();
- applyTransaction(wct, -1 /* syncId */, transition, caller);
+ applyTransaction(wct, -1 /* syncId */, chain, caller);
if (wctApplied != null) {
wctApplied.meet();
}
}
});
} else {
+ final ActionChain chain = mService.mChainTracker.start("startTransit",
+ transition);
transition.start();
- applyTransaction(wct, -1 /* syncId */, transition, caller);
+ applyTransaction(wct, -1 /* syncId */, chain, caller);
if (wctApplied != null) {
wctApplied.meet();
}
@@ -475,7 +488,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */,
false /* isActivityEmbedding */);
syncId = startSyncWithOrganizer(callback);
- applyTransaction(t, syncId, null /* transition */, caller);
+ applyTransaction(t, syncId, mService.mChainTracker.startLegacy("legacyTransit"),
+ caller);
setSyncReady(syncId);
}
} finally {
@@ -493,6 +507,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
try {
synchronized (mGlobalLock) {
final Transition transition = Transition.fromBinder(transitionToken);
+ final ActionChain chain =
+ mService.mChainTracker.startFinish("finishTransit", transition);
// apply the incoming transaction before finish in case it alters the visibility
// of the participants.
if (t != null) {
@@ -500,9 +516,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// changes of the transition participants will only set visible-requested
// and still let finishTransition handle the participants.
mTransitionController.mFinishingTransition = transition;
- applyTransaction(t, -1 /* syncId */, null /*transition*/, caller, transition);
+ applyTransaction(t, -1 /* syncId */, chain, caller);
}
- mTransitionController.finishTransition(transition);
+ mTransitionController.finishTransition(chain);
mTransitionController.mFinishingTransition = null;
}
} finally {
@@ -537,9 +553,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
- if (mTransitionController.getTransitionPlayer() == null) {
+ if (!mTransitionController.isShellTransitionsEnabled()) {
// No need to worry about transition when Shell transition is not enabled.
- applyTransaction(wct, -1 /* syncId */, null /* transition */, caller);
+ applyTransaction(wct, -1 /* syncId */,
+ mService.mChainTracker.startLegacy("legacyTFTransact"), caller);
return;
}
@@ -548,8 +565,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// Although there is an active sync, we want to apply the transaction now.
// TODO(b/232042367) Redesign the organizer update on activity callback so that we
// we will know about the transition explicitly.
- final Transition transition = mTransitionController.getCollectingTransition();
- if (transition == null) {
+ final ActionChain chain = mService.mChainTracker.startDefault("tfTransact");
+ if (chain.mTransition == null) {
// This should rarely happen, and we should try to avoid using
// {@link #applySyncTransaction} with Shell transition.
// We still want to apply and merge the transaction to the active sync
@@ -559,7 +576,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
+ " because there is an ongoing sync for"
+ " applySyncTransaction().");
}
- applyTransaction(wct, -1 /* syncId */, transition, caller);
+ applyTransaction(wct, -1 /* syncId */, chain, caller);
return;
}
@@ -570,8 +587,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
transition.abort();
return;
}
- if (applyTransaction(wct, -1 /* syncId */, transition, caller, deferred)
- == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
+ final ActionChain chain = mService.mChainTracker.start("tfTransact", transition);
+ final int effects = applyTransaction(wct, -1 /* syncId */, chain, caller, deferred);
+ if (effects == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
transition.abort();
return;
}
@@ -586,15 +604,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
- @Nullable Transition transition, @NonNull CallerInfo caller) {
- return applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
- }
-
- private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
- @Nullable Transition transition, @NonNull CallerInfo caller, boolean deferred) {
+ @NonNull ActionChain chain, @NonNull CallerInfo caller, boolean deferred) {
if (deferred) {
try {
- return applyTransaction(t, syncId, transition, caller);
+ return applyTransaction(t, syncId, chain, caller);
} catch (RuntimeException e) {
// If the transaction is deferred, the caller could be from TransitionController
// #tryStartCollectFromQueue that executes on system's worker thread rather than
@@ -604,19 +617,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
return TRANSACT_EFFECTS_NONE;
}
- return applyTransaction(t, syncId, transition, caller);
+ return applyTransaction(t, syncId, chain, caller);
}
/**
* @param syncId If non-null, this will be a sync-transaction.
- * @param transition A transition to collect changes into.
+ * @param chain A lifecycle-chain to acculumate changes into.
* @param caller Info about the calling process.
- * @param finishTransition The transition that is currently being finished.
* @return The effects of the window container transaction.
*/
private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
- @Nullable Transition transition, @NonNull CallerInfo caller,
- @Nullable Transition finishTransition) {
+ @NonNull ActionChain chain, @NonNull CallerInfo caller) {
int effects = TRANSACT_EFFECTS_NONE;
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
@@ -624,20 +635,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
boolean deferResume = true;
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
boolean deferTransitionReady = false;
- if (transition != null && !t.isEmpty()) {
- if (transition.isCollecting()) {
+ if (chain.mTransition != null && !t.isEmpty() && !chain.isFinishing()) {
+ if (chain.mTransition.isCollecting()) {
deferTransitionReady = true;
- transition.deferTransitionReady();
+ chain.mTransition.deferTransitionReady();
} else {
Slog.w(TAG, "Transition is not collecting when applyTransaction."
- + " transition=" + transition + " state=" + transition.getState());
- transition = null;
+ + " transition=" + chain.mTransition + " state="
+ + chain.mTransition.getState());
+ chain.mTransition = null;
}
}
try {
final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
- if (transition != null) {
- transition.applyDisplayChangeIfNeeded(haveConfigChanges);
+ if (chain.mTransition != null) {
+ chain.mTransition.applyDisplayChangeIfNeeded(haveConfigChanges);
if (!haveConfigChanges.isEmpty()) {
effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
}
@@ -645,7 +657,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
final int hopSize = hops.size();
Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries;
- if (transition != null) {
+ if (chain.mTransition != null) {
// Mark any config-at-end containers before applying config changes so that
// the config changes don't dispatch to client.
entries = t.getChanges().entrySet().iterator();
@@ -655,7 +667,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (!entry.getValue().getConfigAtTransitionEnd()) continue;
final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
if (wc == null || !wc.isAttached()) continue;
- transition.setConfigAtEnd(wc);
+ chain.mTransition.setConfigAtEnd(wc);
}
}
entries = t.getChanges().entrySet().iterator();
@@ -672,15 +684,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (syncId >= 0) {
addToSyncSet(syncId, wc);
}
- if (transition != null) transition.collect(wc);
+ chain.collect(wc);
if ((entry.getValue().getChangeMask()
& WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
// Disable entering pip (eg. when recents pretends to finish itself)
- if (finishTransition != null) {
- finishTransition.setCanPipOnFinish(false /* canPipOnFinish */);
- } else if (transition != null) {
- transition.setCanPipOnFinish(false /* canPipOnFinish */);
+ if (chain.mTransition != null) {
+ chain.mTransition.setCanPipOnFinish(false /* canPipOnFinish */);
}
}
// A bit hacky, but we need to detect "remove PiP" so that we can "wrap" the
@@ -728,9 +738,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (hopSize > 0) {
final boolean isInLockTaskMode = mService.isInLockTaskMode();
for (int i = 0; i < hopSize; ++i) {
- effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition,
+ effects |= applyHierarchyOp(hops.get(i), effects, syncId, chain,
isInLockTaskMode, caller, t.getErrorCallbackToken(),
- t.getTaskFragmentOrganizer(), finishTransition);
+ t.getTaskFragmentOrganizer());
}
}
// Queue-up bounds-change transactions for tasks which are now organized. Do
@@ -789,7 +799,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
} finally {
if (deferTransitionReady) {
- transition.continueTransitionReady();
+ chain.mTransition.continueTransitionReady();
}
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
if (deferResume) {
@@ -1079,9 +1089,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
- int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
+ int syncId, @NonNull ActionChain chain, boolean isInLockTaskMode,
@NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
- @Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) {
+ @Nullable ITaskFragmentOrganizer organizer) {
final int type = hop.getType();
switch (type) {
case HIERARCHY_OP_TYPE_REMOVE_TASK: {
@@ -1151,7 +1161,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: {
- effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId,
+ effects |= reparentChildrenTasksHierarchyOp(hop, chain.mTransition, syncId,
isInLockTaskMode);
break;
}
@@ -1204,13 +1214,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (syncId >= 0) {
addToSyncSet(syncId, wc);
}
- if (transition != null) {
- transition.collect(wc);
+ if (chain.mTransition != null) {
+ chain.mTransition.collect(wc);
if (hop.isReparent()) {
if (wc.getParent() != null) {
// Collect the current parent. It's visibility may change as
// a result of this reparenting.
- transition.collect(wc.getParent());
+ chain.mTransition.collect(wc.getParent());
}
if (hop.getNewParent() != null) {
final WindowContainer parentWc =
@@ -1219,7 +1229,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
Slog.e(TAG, "Can't resolve parent window from token");
break;
}
- transition.collect(parentWc);
+ chain.mTransition.collect(parentWc);
}
}
}
@@ -1233,8 +1243,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION: {
- effects |= applyTaskFragmentOperation(hop, transition, isInLockTaskMode, caller,
- errorCallbackToken, organizer);
+ effects |= applyTaskFragmentOperation(hop, chain, isInLockTaskMode,
+ caller, errorCallbackToken, organizer);
break;
}
case HIERARCHY_OP_TYPE_PENDING_INTENT: {
@@ -1348,13 +1358,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
- if (finishTransition == null) break;
+ if (!chain.isFinishing()) break;
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
if (container == null) break;
final Task thisTask = container.asActivityRecord() != null
? container.asActivityRecord().getTask() : container.asTask();
if (thisTask == null) break;
- final Task restoreAt = finishTransition.getTransientLaunchRestoreTarget(container);
+ final Task restoreAt = chain.mTransition.getTransientLaunchRestoreTarget(container);
if (restoreAt == null) break;
final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea();
taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
@@ -1444,7 +1454,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
* {@link #TRANSACT_EFFECTS_LIFECYCLE} or {@link #TRANSACT_EFFECTS_CLIENT_CONFIG}.
*/
private int applyTaskFragmentOperation(@NonNull WindowContainerTransaction.HierarchyOp hop,
- @Nullable Transition transition, boolean isInLockTaskMode, @NonNull CallerInfo caller,
+ @NonNull ActionChain chain, boolean isInLockTaskMode, @NonNull CallerInfo caller,
@Nullable IBinder errorCallbackToken, @Nullable ITaskFragmentOrganizer organizer) {
if (!validateTaskFragmentOperation(hop, errorCallbackToken, organizer)) {
return TRANSACT_EFFECTS_NONE;
@@ -1467,7 +1477,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
createTaskFragment(taskFragmentCreationParams, errorCallbackToken, caller,
- transition);
+ chain.mTransition);
break;
}
case OP_TYPE_DELETE_TASK_FRAGMENT: {
@@ -1484,7 +1494,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
}
- effects |= deleteTaskFragment(taskFragment, transition);
+ effects |= deleteTaskFragment(taskFragment, chain.mTransition);
break;
}
case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
@@ -1533,14 +1543,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
opType, exception);
break;
}
- if (transition != null) {
- transition.collect(activity);
+ if (chain.mTransition != null) {
+ chain.collect(activity);
if (activity.getParent() != null) {
// Collect the current parent. Its visibility may change as a result of
// this reparenting.
- transition.collect(activity.getParent());
+ chain.collect(activity.getParent());
}
- transition.collect(taskFragment);
+ chain.collect(taskFragment);
}
activity.reparent(taskFragment, POSITION_TOP);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
@@ -1696,8 +1706,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// If any TaskFragment in the Task is collected by the transition, we make the decor
// surface visible in sync with the TaskFragment transition. Otherwise, we make the
// decor surface visible immediately.
- final TaskFragment syncTaskFragment = transition != null
- ? task.getTaskFragment(transition.mParticipants::contains)
+ final TaskFragment syncTaskFragment = chain.mTransition != null
+ ? task.getTaskFragment(chain.mTransition.mParticipants::contains)
: null;
if (syncTaskFragment != null) {
@@ -1749,7 +1759,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// The decor surface boost/unboost must be applied after the transition is
// completed. Otherwise, the decor surface could be moved before Shell completes
// the transition, causing flicker.
- runAfterTransition(transition, task::commitDecorSurfaceBoostedState);
+ runAfterTransition(chain.mTransition, task::commitDecorSurfaceBoostedState);
}
break;
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index d2aebdeed99a..d96ebc6655ac 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -284,14 +284,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
static final int ANIMATING_REASON_REMOTE_ANIMATION = 1;
/** It is set for wakefulness transition. */
static final int ANIMATING_REASON_WAKEFULNESS_CHANGE = 1 << 1;
- /** Whether the legacy {@link RecentsAnimation} is running. */
- static final int ANIMATING_REASON_LEGACY_RECENT_ANIMATION = 1 << 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
ANIMATING_REASON_REMOTE_ANIMATION,
ANIMATING_REASON_WAKEFULNESS_CHANGE,
- ANIMATING_REASON_LEGACY_RECENT_ANIMATION,
})
@interface AnimatingReason {}
@@ -2017,14 +2014,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return mStoppedState == STOPPED_STATE_FIRST_LAUNCH;
}
- void setRunningRecentsAnimation(boolean running) {
- if (running) {
- addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
- } else {
- removeAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
- }
- }
-
void setRunningRemoteAnimation(boolean running) {
if (running) {
addAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
@@ -2119,9 +2108,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
if ((animatingReasons & ANIMATING_REASON_WAKEFULNESS_CHANGE) != 0) {
pw.print("wakefulness|");
}
- if ((animatingReasons & ANIMATING_REASON_LEGACY_RECENT_ANIMATION) != 0) {
- pw.print("legacy-recents");
- }
pw.println();
}
if (mUseFifoUiScheduling) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index eed0cf78baf4..e5e153a41dbc 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -128,7 +128,6 @@ import static com.android.server.wm.MoveAnimationSpecProto.FROM;
import static com.android.server.wm.MoveAnimationSpecProto.TO;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -581,7 +580,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* is guaranteed to be cleared.
*/
static final int EXIT_ANIMATING_TYPES = ANIMATION_TYPE_APP_TRANSITION
- | ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS;
+ | ANIMATION_TYPE_WINDOW_ANIMATION;
/** Currently running an exit animation? */
boolean mAnimatingExit;
@@ -1971,13 +1970,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* it must be drawn before allDrawn can become true.
*/
boolean isInteresting() {
- final RecentsAnimationController recentsAnimationController =
- mWmService.getRecentsAnimationController();
return mActivityRecord != null
&& (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
- && mViewVisibility == View.VISIBLE
- && (recentsAnimationController == null
- || recentsAnimationController.isInterestingForAllDrawn(this));
+ && mViewVisibility == View.VISIBLE;
}
/**
@@ -4671,17 +4666,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (!isImeLayeringTarget()) {
return false;
}
- if (!com.android.window.flags.Flags.doNotSkipImeByTargetVisibility()) {
- // Note that we don't process IME window if the IME input target is not on the screen.
- // In case some unexpected IME visibility cases happen like starting the remote
- // animation on the keyguard but seeing the IME window that originally on the app
- // which behinds the keyguard.
- final WindowState imeInputTarget = getImeInputTarget();
- if (imeInputTarget != null
- && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) {
- return false;
- }
- }
return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
}
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
index 2c5a4538863e..dc048ef8c8ec 100644
--- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -35,7 +35,6 @@ import android.util.proto.ProtoInputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance,
WindowTracingDataSource.TlsState, Void> {
@@ -77,15 +76,11 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS
private static final String TAG = "WindowTracingDataSource";
@NonNull
- private final WeakReference<Consumer<Config>> mOnStartCallback;
- @NonNull
- private final WeakReference<Consumer<Config>> mOnStopCallback;
+ private final WeakReference<WindowTracingPerfetto> mWindowTracing;
- public WindowTracingDataSource(@NonNull Consumer<Config> onStart,
- @NonNull Consumer<Config> onStop) {
+ public WindowTracingDataSource(WindowTracingPerfetto windowTracing) {
super(DATA_SOURCE_NAME);
- mOnStartCallback = new WeakReference(onStart);
- mOnStopCallback = new WeakReference(onStop);
+ mWindowTracing = new WeakReference<>(windowTracing);
Producer.init(InitArguments.DEFAULTS);
DataSourceParams params =
@@ -94,6 +89,7 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS
PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
.build();
register(params);
+ Log.i(TAG, "Registered with perfetto service");
}
@Override
@@ -103,17 +99,17 @@ public final class WindowTracingDataSource extends DataSource<WindowTracingDataS
return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) {
@Override
protected void onStart(StartCallbackArguments args) {
- Consumer<Config> callback = mOnStartCallback.get();
- if (callback != null) {
- callback.accept(mConfig);
+ WindowTracingPerfetto windowTracing = mWindowTracing.get();
+ if (windowTracing != null) {
+ windowTracing.onStart(mConfig);
}
}
@Override
protected void onStop(StopCallbackArguments args) {
- Consumer<Config> callback = mOnStopCallback.get();
- if (callback != null) {
- callback.accept(mConfig);
+ WindowTracingPerfetto windowTracing = mWindowTracing.get();
+ if (windowTracing != null) {
+ windowTracing.onStop(mConfig);
}
}
};
diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
index cf948ca76f99..22d6c863fd4f 100644
--- a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
+++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
@@ -35,8 +35,7 @@ class WindowTracingPerfetto extends WindowTracing {
private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger();
private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger();
- private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(
- this::onStart, this::onStop);
+ private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(this);
WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) {
this(service, choreographer, service.mGlobalLock);
@@ -156,7 +155,7 @@ class WindowTracingPerfetto extends WindowTracing {
return mCountSessionsOnTransaction.get() > 0;
}
- private void onStart(WindowTracingDataSource.Config config) {
+ void onStart(WindowTracingDataSource.Config config) {
if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
mCountSessionsOnFrame.incrementAndGet();
} else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
@@ -168,7 +167,7 @@ class WindowTracingPerfetto extends WindowTracing {
log(WHERE_START_TRACING);
}
- private void onStop(WindowTracingDataSource.Config config) {
+ void onStop(WindowTracingDataSource.Config config) {
if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
mCountSessionsOnFrame.decrementAndGet();
} else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1290fb7ef91a..a80ee0f66742 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2726,22 +2726,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
- if (Flags.securityLogV2Enabled()) {
- boolean auditLoggingEnabled = Boolean.TRUE.equals(
- mDevicePolicyEngine.getResolvedPolicy(
- PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
- boolean securityLoggingEnabled = Boolean.TRUE.equals(
- mDevicePolicyEngine.getResolvedPolicy(
- PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
- setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
- mInjector.runCryptoSelfTest();
- } else {
- synchronized (getLockObject()) {
- mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
- mInjector.runCryptoSelfTest();
- maybePauseDeviceWideLoggingLocked();
- }
- }
+ boolean auditLoggingEnabled = Boolean.TRUE.equals(
+ mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
+ boolean securityLoggingEnabled = Boolean.TRUE.equals(
+ mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
+ setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
+ mInjector.runCryptoSelfTest();
}
/**
@@ -3399,7 +3391,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@GuardedBy("getLockObject()")
private void maybeMigrateSecurityLoggingPolicyLocked() {
- if (!Flags.securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) {
+ if (mOwners.isSecurityLoggingMigrated()) {
return;
}
@@ -16304,9 +16296,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void enforceSecurityLoggingPolicy(boolean enabled) {
- if (!Flags.securityLogV2Enabled()) {
- return;
- }
Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL);
enforceLoggingPolicy(enabled, Boolean.TRUE.equals(auditLoggingEnabled));
@@ -16314,9 +16303,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void enforceAuditLoggingPolicy(boolean enabled) {
- if (!Flags.securityLogV2Enabled()) {
- return;
- }
Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL);
enforceLoggingPolicy(Boolean.TRUE.equals(securityLoggingEnabled), enabled);
@@ -18252,45 +18238,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final CallerIdentity caller = getCallerIdentity(who, packageName);
- if (Flags.securityLogV2Enabled()) {
- EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- caller.getPackageName(),
- caller.getUserId());
- if (enabled) {
- mDevicePolicyEngine.setGlobalPolicy(
- PolicyDefinition.SECURITY_LOGGING,
- admin,
- new BooleanPolicyValue(true));
- } else {
- mDevicePolicyEngine.removeGlobalPolicy(
- PolicyDefinition.SECURITY_LOGGING,
- admin);
- }
+ EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
+ who,
+ MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+ caller.getPackageName(),
+ caller.getUserId());
+ if (enabled) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.SECURITY_LOGGING,
+ admin,
+ new BooleanPolicyValue(true));
} else {
- synchronized (getLockObject()) {
- if (who != null) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDefaultDeviceOwner(caller));
- } else {
- // A delegate app passes a null admin component, which is expected
- Preconditions.checkCallAuthorization(
- isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
- }
-
- if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
- return;
- }
- mInjector.securityLogSetLoggingEnabledProperty(enabled);
- if (enabled) {
- mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
- maybePauseDeviceWideLoggingLocked();
- } else {
- mSecurityLogMonitor.stop();
- }
- }
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.SECURITY_LOGGING,
+ admin);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_SECURITY_LOGGING_ENABLED)
@@ -18312,29 +18273,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mInjector.securityLogGetLoggingEnabledProperty();
}
- if (Flags.securityLogV2Enabled()) {
- final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- caller.getPackageName(),
- caller.getUserId());
- final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
- PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
- return Boolean.TRUE.equals(policy);
- } else {
- synchronized (getLockObject()) {
- if (admin != null) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDefaultDeviceOwner(caller));
- } else {
- // A delegate app passes a null admin component, which is expected
- Preconditions.checkCallAuthorization(
- isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
- }
- return mInjector.securityLogGetLoggingEnabledProperty();
- }
- }
+ final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ admin,
+ MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+ caller.getPackageName(),
+ caller.getUserId());
+ final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
+ return Boolean.TRUE.equals(policy);
}
private void recordSecurityLogRetrievalTime() {
@@ -18410,42 +18356,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- if (Flags.securityLogV2Enabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
- caller.getPackageName(),
- caller.getUserId());
-
- synchronized (getLockObject()) {
- Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
- || areAllUsersAffiliatedWithDeviceLocked());
- }
-
- Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
- PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ admin,
+ MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+ caller.getPackageName(),
+ caller.getUserId());
- if (!Boolean.TRUE.equals(policy)) {
- Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs",
- caller.getPackageName());
- return null;
- }
- } else {
- if (admin != null) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDefaultDeviceOwner(caller));
- } else {
- // A delegate app passes a null admin component, which is expected
- Preconditions.checkCallAuthorization(
- isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
- }
+ synchronized (getLockObject()) {
Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
|| areAllUsersAffiliatedWithDeviceLocked());
+ }
- if (!mInjector.securityLogGetLoggingEnabledProperty()) {
- return null;
- }
+ Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
+
+ if (!Boolean.TRUE.equals(policy)) {
+ Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs",
+ caller.getPackageName());
+ return null;
}
recordSecurityLogRetrievalTime();
@@ -18465,10 +18393,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final CallerIdentity caller = getCallerIdentity(callingPackage);
- if (!Flags.securityLogV2Enabled()) {
- throw new UnsupportedOperationException("Audit log not enabled");
- }
-
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
null /* admin */,
MANAGE_DEVICE_POLICY_AUDIT_LOGGING,
@@ -18493,10 +18417,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
- if (!Flags.securityLogV2Enabled()) {
- throw new UnsupportedOperationException("Audit log not enabled");
- }
-
final CallerIdentity caller = getCallerIdentity(callingPackage);
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
null /* admin */,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 2ea5f168bdd1..52a784559510 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -410,9 +410,8 @@ class OwnersData {
out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
- if (Flags.securityLogV2Enabled()) {
- out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
- }
+ out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
+
if (Flags.unmanagedModeMigration()) {
out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED,
mRequiredPasswordComplexityMigrated);
@@ -483,8 +482,8 @@ class OwnersData {
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
null, ATTR_MIGRATED_POST_UPGRADE, false);
- mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
- && parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+ mSecurityLoggingMigrated =
+ parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration()
&& parser.getAttributeBoolean(null,
ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index dd0493032c56..474c48a746c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -23,7 +23,6 @@ import android.app.admin.DeviceAdminReceiver;
import android.app.admin.IAuditLogEventsCallback;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
-import android.app.admin.flags.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
@@ -184,28 +183,6 @@ class SecurityLogMonitor implements Runnable {
@GuardedBy("mLock")
private final ArrayDeque<SecurityEvent> mAuditLogEventBuffer = new ArrayDeque<>();
- /**
- * Start security logging.
- *
- * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all
- * users on the device.
- */
- void start(int enabledUser) {
- Slog.i(TAG, "Starting security logging for user " + enabledUser);
- mEnabledUser = enabledUser;
- mLock.lock();
- try {
- if (mMonitorThread == null) {
- resetLegacyBufferLocked();
- startMonitorThreadLocked();
- } else {
- Slog.i(TAG, "Security log monitor thread is already running");
- }
- } finally {
- mLock.unlock();
- }
- }
-
void stop() {
Slog.i(TAG, "Stopping security logging.");
mLock.lock();
@@ -467,11 +444,11 @@ class SecurityLogMonitor implements Runnable {
assignLogId(event);
}
- if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
+ if (mLegacyLogEnabled) {
addToLegacyBufferLocked(dedupedLogs);
}
- if (Flags.securityLogV2Enabled() && mAuditLogEnabled) {
+ if (mAuditLogEnabled) {
addAuditLogEventsLocked(dedupedLogs);
}
}
@@ -548,7 +525,7 @@ class SecurityLogMonitor implements Runnable {
saveLastEvents(newLogs);
newLogs.clear();
- if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
+ if (mLegacyLogEnabled) {
notifyDeviceOwnerOrProfileOwnerIfNeeded(force);
}
} catch (IOException e) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 09c54cb40373..da3ada8671df 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -106,7 +106,7 @@ import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.policy.AttributeCache;
-import com.android.internal.protolog.ProtoLogService;
+import com.android.internal.protolog.ProtoLogConfigurationService;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.FrameworkStatsLog;
@@ -256,6 +256,7 @@ import com.android.server.stats.bootstrap.StatsBootstrapAtomService;
import com.android.server.stats.pull.StatsPullAtomService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.supervision.SupervisionService;
import com.android.server.systemcaptions.SystemCaptionsManagerService;
import com.android.server.telecom.TelecomLoaderService;
import com.android.server.testharness.TestHarnessModeService;
@@ -1092,8 +1093,9 @@ public final class SystemServer implements Dumpable {
// Orchestrates some ProtoLogging functionality.
if (android.tracing.Flags.clientSideProtoLogging()) {
- t.traceBegin("StartProtoLogService");
- ServiceManager.addService(Context.PROTOLOG_SERVICE, new ProtoLogService());
+ t.traceBegin("StartProtoLogConfigurationService");
+ ServiceManager.addService(
+ Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService());
t.traceEnd();
}
@@ -1597,6 +1599,12 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
t.traceEnd();
+ if (android.app.supervision.flags.Flags.supervisionApi()) {
+ t.traceBegin("StartSupervisionService");
+ mSystemServiceManager.startService(SupervisionService.Lifecycle.class);
+ t.traceEnd();
+ }
+
if (!isTv) {
t.traceBegin("StartVibratorManagerService");
mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
diff --git a/ravenwood/coretest/Android.bp b/services/supervision/Android.bp
index a78c5c1e8227..93a0c4af7891 100644
--- a/ravenwood/coretest/Android.bp
+++ b/services/supervision/Android.bp
@@ -7,17 +7,16 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-android_ravenwood_test {
- name: "RavenwoodCoreTest",
+filegroup {
+ name: "services.supervision-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
- static_libs: [
- "androidx.annotation_annotation",
- "androidx.test.ext.junit",
- "androidx.test.rules",
- ],
- srcs: [
- "test/**/*.java",
- ],
- sdk_version: "test_current",
- auto_gen_config: true,
+java_library_static {
+ name: "services.supervision",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.supervision-sources"],
+ libs: ["services.core"],
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
new file mode 100644
index 000000000000..a4ef629492e7
--- /dev/null
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.supervision;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.supervision.ISupervisionManager;
+import android.content.Context;
+
+
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/** Service for handling system supervision. */
+public class SupervisionService extends ISupervisionManager.Stub {
+ private static final String LOG_TAG = "SupervisionService";
+
+ private final Context mContext;
+
+ public SupervisionService(Context context) {
+ mContext = context.createAttributionContext("SupervisionService");
+ }
+
+ @Override
+ public boolean isSupervisionEnabled() {
+ return false;
+ }
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd,
+ @NonNull PrintWriter fout, @Nullable String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
+
+ fout.println("Supervision enabled: " + isSupervisionEnabled());
+ }
+
+ public static class Lifecycle extends SystemService {
+ private final SupervisionService mSupervisionService;
+
+ public Lifecycle(@NonNull Context context) {
+ super(context);
+ mSupervisionService = new SupervisionService(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
+ }
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 0787058e3c11..2c785049412a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -33,11 +33,15 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.util.Log;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicyConstants;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
@@ -56,6 +60,7 @@ import com.android.internal.inputmethod.InputMethodNavButtonFlags;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,6 +94,9 @@ public class InputMethodServiceTest {
private String mInputMethodId;
private boolean mShowImeWithHardKeyboardEnabled;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -155,7 +163,13 @@ public class InputMethodServiceTest {
() -> assertThat(mUiDevice.pressHome()).isTrue(),
true /* expected */,
false /* inputViewStarted */);
- assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ if (Flags.refactorInsetsController()) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
/**
@@ -182,8 +196,13 @@ public class InputMethodServiceTest {
/**
* This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
+ *
+ * With the refactor in b/298172246, all calls to IMMS#{show,hide}MySoftInputLocked
+ * will be just apply the requested visibility (by using the callback). Therefore, we will
+ * lose flags like HIDE_IMPLICIT_ONLY.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowHideSelf() throws Exception {
setShowImeWithHardKeyboard(true /* enabled */);
@@ -375,8 +394,13 @@ public class InputMethodServiceTest {
/**
* This checks that an implicit show request when the IME is not previously shown,
* and it should be shown in fullscreen mode, results in the IME not being shown.
+ *
+ * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+ * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+ * SHOW_IMPLICIT.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
setShowImeWithHardKeyboard(true /* enabled */);
@@ -425,8 +449,13 @@ public class InputMethodServiceTest {
/**
* This checks that an implicit show request when a hard keyboard is connected,
* results in the IME not being shown.
+ *
+ * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+ * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+ * SHOW_IMPLICIT.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_withHardKeyboard() throws Exception {
setShowImeWithHardKeyboard(false /* enabled */);
@@ -484,8 +513,13 @@ public class InputMethodServiceTest {
* This checks that an implicit show request followed by connecting a hard keyboard
* and a configuration change, does not trigger IMS#onFinishInputView,
* but results in the IME being hidden.
+ *
+ * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+ * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+ * SHOW_IMPLICIT.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputImplicitly_thenConfigurationChanged() throws Exception {
setShowImeWithHardKeyboard(false /* enabled */);
@@ -567,8 +601,13 @@ public class InputMethodServiceTest {
* This checks that a forced show request directly followed by an explicit show request,
* and then a hide not always request, still results in the IME being shown
* (i.e. the explicit show request retains the forced state).
+ *
+ * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+ * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+ * HIDE_NOT_ALWAYS.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways()
throws Exception {
setShowImeWithHardKeyboard(true /* enabled */);
@@ -734,7 +773,13 @@ public class InputMethodServiceTest {
backButtonUiObject.click();
mInstrumentation.waitForIdleSync();
- assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ if (Flags.refactorInsetsController()) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
/**
@@ -766,7 +811,13 @@ public class InputMethodServiceTest {
backButtonUiObject.longClick();
mInstrumentation.waitForIdleSync();
- assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ if (Flags.refactorInsetsController()) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
/**
@@ -848,7 +899,13 @@ public class InputMethodServiceTest {
assertWithMessage("Input Method Switcher Menu is shown")
.that(isInputMethodPickerShown(imm))
.isTrue();
- assertThat(mInputMethodService.isInputViewShown()).isTrue();
+ if (Flags.refactorInsetsController()) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+ }
// Hide the Picker menu before finishing.
mUiDevice.pressBack();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index ab0f0c1fe5ff..d91f154c1b87 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -3556,12 +3556,16 @@ public class DisplayModeDirectorTest {
new RefreshRateRange(refreshRate, refreshRate);
displayListener.onDisplayChanged(DISPLAY_ID);
- Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, refreshRate, refreshRate);
mInjector.mDisplayInfo.layoutLimitedRefreshRate = null;
displayListener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
+ assertNull(vote);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
assertNull(vote);
}
@@ -3585,6 +3589,8 @@ public class DisplayModeDirectorTest {
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
assertNull(vote);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
+ assertNull(vote);
}
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index ee96c2abbf46..3dd2f24aa4e4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -22,6 +22,7 @@ import static com.android.server.am.ActivityManagerService.Injector;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -42,6 +43,8 @@ import android.os.Process;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.appop.AppOpsService;
@@ -121,11 +124,18 @@ public class ApplicationStartInfoTest {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+ mAppStartInfoTracker.mMonotonicClock = new MonotonicClock(
+ Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
mAppStartInfoTracker.clearProcessStartInfo(true);
mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
mAppStartInfoTracker.mAppStartInfoHistoryListSize =
mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());
+
+ mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
+ AppStartInfoTracker.APP_START_STORE_DIR);
+ mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
+ AppStartInfoTracker.APP_START_INFO_FILE);
}
@After
@@ -135,11 +145,8 @@ public class ApplicationStartInfoTest {
@Test
public void testApplicationStartInfo() throws Exception {
- mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
- AppStartInfoTracker.APP_START_STORE_DIR);
+ // Make sure we can write to the file.
assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
- mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
- AppStartInfoTracker.APP_START_INFO_FILE);
final long appStartTimestampIntentStarted = 1000000;
final long appStartTimestampActivityLaunchFinished = 2000000;
@@ -482,6 +489,79 @@ public class ApplicationStartInfoTest {
verifyInProgressRecordsSize(AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS);
}
+ /**
+ * Test to make sure that records are returned in correct order, from most recently added at
+ * index 0 to least recently added at index size - 1.
+ */
+ @Test
+ public void testHistoricalRecordsOrdering() throws Exception {
+ // Clear old records
+ mAppStartInfoTracker.clearProcessStartInfo(false);
+
+ // Add some records with timestamps 0 decreasing as clock increases.
+ ProcessRecord app = makeProcessRecord(
+ APP_1_PID_1, // pid
+ APP_1_UID, // uid
+ APP_1_UID, // packageUid
+ null, // definingUid
+ APP_1_PROCESS_NAME, // processName
+ APP_1_PACKAGE_NAME); // packageName
+
+ mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT),
+ false /* isAlarm */);
+ mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT),
+ false /* isAlarm */);
+ mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT),
+ false /* isAlarm */);
+
+ // Get records
+ ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+ mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
+
+ // Confirm that records are in correct order, with index 0 representing the most recently
+ // added record and index size - 1 representing the least recently added one.
+ assertEquals(3, list.size());
+ assertEquals(1L, list.get(0).getStartupTimestamps().get(0).longValue());
+ assertEquals(2L, list.get(1).getStartupTimestamps().get(0).longValue());
+ assertEquals(3L, list.get(2).getStartupTimestamps().get(0).longValue());
+ }
+
+ /**
+ * Test to make sure that persist and restore correctly maintains the state of the monotonic
+ * clock.
+ */
+ @Test
+ public void testPersistAndRestoreMonotonicClock() {
+ // Make sure we can write to the file.
+ assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
+
+ // No need to persist records for this test, clear any that may be there.
+ mAppStartInfoTracker.clearProcessStartInfo(false);
+
+ // Set clock with an arbitrary 5 minute offset, just needs to be longer than it would take
+ // for code to run.
+ mAppStartInfoTracker.mMonotonicClock = new MonotonicClock(5 * 60 * 1000,
+ Clock.SYSTEM_CLOCK);
+
+ // Record the current time.
+ long originalMonotonicTime = mAppStartInfoTracker.mMonotonicClock.monotonicTime();
+
+ // Now persist the process start info. Records were cleared above so this should just
+ // persist the monotonic time.
+ mAppStartInfoTracker.persistProcessStartInfo();
+
+ // Null out the clock to make sure its set on load.
+ mAppStartInfoTracker.mMonotonicClock = null;
+ assertNull(mAppStartInfoTracker.mMonotonicClock);
+
+ // Now load from disk.
+ mAppStartInfoTracker.loadExistingProcessStartInfo();
+
+ // Confirm clock has been set and that its current time is greater than the previous one.
+ assertNotNull(mAppStartInfoTracker.mMonotonicClock);
+ assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime);
+ }
+
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
try {
Field field = clazz.getDeclaredField(fieldName);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 0ba74c62b9fe..100b54897573 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1176,6 +1176,17 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
verifyPendingRecords(redQueue, List.of(screenOff));
verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
+
+ final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false);
+ screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+ "testDeliveryGroupPolicy_prioritized_diffReceivers");
+ mImpl.enqueueBroadcastLocked(screenOffRecord);
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 0703db2ab648..18811de14c73 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -223,6 +223,11 @@ public class GameManagerServiceTests {
mShutDownActionReceiver = receiver;
return null;
}
+
+ @Override
+ public int getUserId() {
+ return 0;
+ }
}
@Before
@@ -237,7 +242,7 @@ public class GameManagerServiceTests {
mPackageCategories = new HashMap<>();
mPackageUids = new HashMap<>();
mPackageName = mMockContext.getPackageName();
- mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME, -1);
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
@@ -245,7 +250,12 @@ public class GameManagerServiceTests {
}
private void mockAppCategory(String packageName, int packageUid,
- @ApplicationInfo.Category int category)
+ @ApplicationInfo.Category int category) throws Exception {
+ mockAppCategory(packageName, packageUid, category, -1 /*userId*/);
+ }
+
+ private void mockAppCategory(String packageName, int packageUid,
+ @ApplicationInfo.Category int category, int userId)
throws Exception {
reset(mMockPackageManager);
mPackageCategories.put(packageName, category);
@@ -259,8 +269,15 @@ public class GameManagerServiceTests {
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.packageName = packageName;
applicationInfo.category = category;
- when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt()))
- .thenReturn(applicationInfo);
+ if (userId == -1) {
+ when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(),
+ anyInt()))
+ .thenReturn(applicationInfo);
+ } else {
+ when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(),
+ eq(userId)))
+ .thenReturn(applicationInfo);
+ }
final PackageInfo pi = new PackageInfo();
pi.packageName = packageName;
@@ -2331,10 +2348,12 @@ public class GameManagerServiceTests {
@Test
public void testGamePowerMode_twoGames() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
String someGamePkg = "some.game";
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME,
+ ActivityManager.getCurrentUser());
HashMap<Integer, Boolean> powerState = new HashMap<>();
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
@@ -2354,10 +2373,12 @@ public class GameManagerServiceTests {
@Test
public void testGamePowerMode_twoGamesOverlap() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
String someGamePkg = "some.game";
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME,
+ ActivityManager.getCurrentUser());
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2372,7 +2393,8 @@ public class GameManagerServiceTests {
@Test
public void testGamePowerMode_noPackage() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
String[] packages = {};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2383,23 +2405,24 @@ public class GameManagerServiceTests {
@Test
public void testGamePowerMode_gameAndNotGameApps_flagOn() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
-
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ int userId = ActivityManager.getCurrentUser();
String nonGamePkg1 = "not.game1";
int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
- mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE, userId);
String nonGamePkg2 = "not.game2";
int nonGameUid2 = DEFAULT_PACKAGE_UID + 2;
- mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE, userId);
String gamePkg1 = "game1";
int gameUid1 = DEFAULT_PACKAGE_UID + 3;
- mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME, userId);
String gamePkg2 = "game2";
int gameUid2 = DEFAULT_PACKAGE_UID + 4;
- mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME, userId);
// non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2470,15 +2493,17 @@ public class GameManagerServiceTests {
@Test
public void testGamePowerMode_gameAndNotGameApps_flagOff() throws Exception {
mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ int userId = ActivityManager.getCurrentUser();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
String nonGamePkg1 = "not.game1";
int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
- mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE, userId);
String gamePkg1 = "game1";
int gameUid1 = DEFAULT_PACKAGE_UID + 3;
- mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME, userId);
// non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index bbab0eef49cb..7b635d4198c3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -38,6 +38,7 @@ import android.hardware.power.stats.PowerEntity;
import android.hardware.power.stats.StateResidencyResult;
import android.os.Handler;
import android.os.Looper;
+import android.os.connectivity.WifiActivityEnergyInfo;
import android.platform.test.ravenwood.RavenwoodRule;
import android.power.PowerStatsInternal;
import android.util.IntArray;
@@ -88,6 +89,33 @@ public class BatteryExternalStatsWorkerTest {
}
@Test
+ public void testUpdateWifiState() {
+ WifiActivityEnergyInfo firstInfo = new WifiActivityEnergyInfo(1111,
+ WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 11, 22, 33, 44);
+
+ WifiActivityEnergyInfo delta = mBatteryExternalStatsWorker.extractDeltaLocked(firstInfo);
+
+ assertEquals(1111, delta.getTimeSinceBootMillis());
+ assertEquals(WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, delta.getStackState());
+ assertEquals(0, delta.getControllerTxDurationMillis());
+ assertEquals(0, delta.getControllerRxDurationMillis());
+ assertEquals(0, delta.getControllerScanDurationMillis());
+ assertEquals(0, delta.getControllerIdleDurationMillis());
+
+ WifiActivityEnergyInfo secondInfo = new WifiActivityEnergyInfo(91111,
+ WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, 811, 722, 633, 544);
+
+ delta = mBatteryExternalStatsWorker.extractDeltaLocked(secondInfo);
+
+ assertEquals(91111, delta.getTimeSinceBootMillis());
+ assertEquals(WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, delta.getStackState());
+ assertEquals(800, delta.getControllerTxDurationMillis());
+ assertEquals(700, delta.getControllerRxDurationMillis());
+ assertEquals(600, delta.getControllerScanDurationMillis());
+ assertEquals(500, delta.getControllerIdleDurationMillis());
+ }
+
+ @Test
public void testTargetedEnergyConsumerQuerying() {
final int numCpuClusters = 4;
final int numDisplays = 5;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
index 127ab8a6e549..f22279a88a50 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
@@ -74,6 +74,7 @@ public class GnssPowerStatsTest {
private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
private static final int VOLTAGE_MV = 3500;
private static final int ENERGY_CONSUMER_ID = 777;
+ private static final long START_TIME = 10_000_000_000L;
private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
@Mock
@@ -113,11 +114,13 @@ public class GnssPowerStatsTest {
};
private MonotonicClock mMonotonicClock;
+ private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+ mMonotonicClock = new MonotonicClock(START_TIME, mStatsRule.getMockClock());
+ mHistoryItem.clear();
}
@Test
@@ -129,7 +132,6 @@ public class GnssPowerStatsTest {
PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
() -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
- stats.start(0);
GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
collector.addConsumer(
@@ -142,9 +144,11 @@ public class GnssPowerStatsTest {
stats.noteStateChange(buildHistoryItem(0, true, APP_UID1));
// Turn the screen off after 2.5 seconds
- stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
- stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
- stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+ START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ START_TIME + 5000);
stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
@@ -158,7 +162,87 @@ public class GnssPowerStatsTest {
mStatsRule.setTime(11_000, 11_000);
collector.collectAndDeliverStats();
- stats.finish(11_000);
+ stats.finish(START_TIME + 11_000);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+ statsLayout.fromExtras(descriptor.extras);
+
+ // scr-on, GNSS-good: 2500 * 100 = 250000 mA-ms = 0.06944 mAh
+ // scr-off GNSS=good: 4500 * 100 = 0.12500 mAh
+ // scr-off GNSS=poor: 3000 * 1000 = 0.83333 mAh
+ // scr-off GNSS-on: 0.12500 + 0.83333 = 0.95833 mAh
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.06944);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.12500 + 0.83333);
+
+ // UID1 =
+ // scr-on FG: 2500 -> 0.06944 mAh
+ // scr-off BG: 2500/7500 * 0.95833 = 0.31944 mAh
+ // scr-off FGS: 1000/7500 * 0.95833 = 0.12777 mAh
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.06944);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.31944);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.12777);
+
+ // UID2 =
+ // scr-off cached: 4000/7500 * 0.95833 = 0.51111 mAh
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.51111);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0);
+ }
+
+ @Test
+ public void initialStateGnssOn() {
+ // ODPM unsupported
+ when(mConsumedEnergyRetriever
+ .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS), any()))
+ .thenReturn(new int[0]);
+
+ PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
+ () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
+
+ stats.noteStateChange(buildHistoryItemInitialStateGpsOn(0));
+
+ // Turn the screen off after 2.5 seconds
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+ START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ START_TIME + 5000);
+
+ stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
+
+ stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2));
+ stats.noteStateChange(buildHistoryItem(7000,
+ GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+ stats.noteStateChange(buildHistoryItem(8000,
+ GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+ mStatsRule.setTime(11_000, 11_000);
+
+ stats.finish(START_TIME + 11_000);
PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
@@ -224,8 +308,6 @@ public class GnssPowerStatsTest {
powerStats -> stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime()));
collector.setEnabled(true);
- stats.start(0);
-
// Establish a baseline
when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID}))
.thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 10000));
@@ -234,9 +316,11 @@ public class GnssPowerStatsTest {
stats.noteStateChange(buildHistoryItem(0, true, APP_UID1));
// Turn the screen off after 2.5 seconds
- stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
- stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
- stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+ START_TIME + 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ START_TIME + 5000);
stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
@@ -245,16 +329,14 @@ public class GnssPowerStatsTest {
collector.collectAndDeliverStats();
stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2));
- stats.noteStateChange(buildHistoryItem(7000,
- GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
- stats.noteStateChange(buildHistoryItem(8000,
- GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+ stats.noteStateChange(buildHistoryItem(7000, GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+ stats.noteStateChange(buildHistoryItem(8000, GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
mStatsRule.setTime(11_000, 11_000);
when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID}))
.thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 3_610_000));
collector.collectAndDeliverStats();
- stats.finish(11_000);
+ stats.finish(START_TIME + 11_000);
PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
@@ -313,33 +395,45 @@ public class GnssPowerStatsTest {
.isWithin(PRECISION).of(0);
}
- private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
+ private BatteryStats.HistoryItem buildHistoryItemInitialStateGpsOn(long timestamp) {
+ mStatsRule.setTime(timestamp, timestamp);
+ mHistoryItem.time = mMonotonicClock.monotonicTime();
+ mHistoryItem.states = BatteryStats.HistoryItem.STATE_GPS_ON_FLAG;
+ setGnssSignalLevel(BatteryStats.HistoryItem.GNSS_SIGNAL_QUALITY_NONE);
+ return mHistoryItem;
+ }
+
+ private BatteryStats.HistoryItem buildHistoryItem(long timestamp, boolean stateOn,
int uid) {
mStatsRule.setTime(timestamp, timestamp);
- BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
- historyItem.time = mMonotonicClock.monotonicTime();
- historyItem.states = stateOn ? BatteryStats.HistoryItem.STATE_GPS_ON_FLAG : 0;
+ mHistoryItem.time = mMonotonicClock.monotonicTime();
+ mHistoryItem.states = stateOn ? BatteryStats.HistoryItem.STATE_GPS_ON_FLAG : 0;
if (stateOn) {
- historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
| BatteryStats.HistoryItem.EVENT_FLAG_START;
} else {
- historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
| BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
}
- historyItem.eventTag = historyItem.localEventTag;
- historyItem.eventTag.uid = uid;
- historyItem.eventTag.string = "gnss";
- return historyItem;
+ mHistoryItem.eventTag = mHistoryItem.localEventTag;
+ mHistoryItem.eventTag.uid = uid;
+ mHistoryItem.eventTag.string = "gnss";
+ return mHistoryItem;
}
- private BatteryStats.HistoryItem buildHistoryItem(int timestamp, int signalLevel) {
+ private BatteryStats.HistoryItem buildHistoryItem(long timestamp, int signalLevel) {
mStatsRule.setTime(timestamp, timestamp);
- BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
- historyItem.time = mMonotonicClock.monotonicTime();
- historyItem.states = BatteryStats.HistoryItem.STATE_GPS_ON_FLAG;
- historyItem.states2 =
- signalLevel << BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
- return historyItem;
+ mHistoryItem.time = mMonotonicClock.monotonicTime();
+ setGnssSignalLevel(signalLevel);
+ mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_NONE;
+ mHistoryItem.eventTag = null;
+ return mHistoryItem;
+ }
+
+ private void setGnssSignalLevel(int signalLevel) {
+ mHistoryItem.states2 =
+ (mHistoryItem.states2 & ~BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+ | signalLevel << BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
}
private int[] states(int... states) {
@@ -362,12 +456,14 @@ public class GnssPowerStatsTest {
AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
PowerComponentAggregatedPowerStats powerComponentStats =
aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_GNSS);
- powerComponentStats.start(0);
-
- powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
- powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
- powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
- powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+ powerComponentStats.start(START_TIME);
+
+ powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, START_TIME);
+ powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, START_TIME);
+ powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND,
+ START_TIME);
+ powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED,
+ START_TIME);
return powerComponentStats;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index 9083a1e28e2c..19041451c8eb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -1319,7 +1319,6 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest {
final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
when(window.getWindowInfo()).thenReturn(windowInfo);
- when(window.ignoreRecentsAnimationForAccessibility()).thenReturn(false);
when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
when(window.isTouchable()).thenReturn(true);
when(window.getType()).thenReturn(windowInfo.type);
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index e45ab319146c..beed0a3d413c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -111,6 +111,9 @@ public class VolumeHelperTest {
private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+ /** Choose a default stream volume value which does not depend on min/max. */
+ private static final int DEFAULT_STREAM_VOLUME = 2;
+
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -144,6 +147,8 @@ public class VolumeHelperTest {
private TestLooper mTestLooper;
+ private boolean mIsAutomotive;
+
public static final int[] BASIC_VOLUME_BEHAVIORS = {
AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE,
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL,
@@ -232,9 +237,10 @@ public class VolumeHelperTest {
|| packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
final boolean isSingleVolume = mContext.getResources().getBoolean(
Resources.getSystem().getIdentifier("config_single_volume", "bool", "android"));
- final boolean automotiveHardened = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_AUTOMOTIVE) && autoPublicVolumeApiHardening();
- assumeFalse("Skipping test for fixed, TV, single volume and auto devices",
+ mIsAutomotive = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
+ final boolean automotiveHardened = mIsAutomotive && autoPublicVolumeApiHardening();
+ assumeFalse("Skipping test for fixed, TV, single volume and auto hardened devices",
useFixedVolume || isTelevision || isSingleVolume || automotiveHardened);
InstrumentationRegistry.getInstrumentation().getUiAutomation()
@@ -249,15 +255,14 @@ public class VolumeHelperTest {
{STREAM_MUSIC, STREAM_NOTIFICATION, STREAM_RING, STREAM_ALARM, STREAM_SYSTEM,
STREAM_VOICE_CALL, STREAM_ACCESSIBILITY};
for (int streamType : usedStreamTypes) {
- final int streamVolume = (mAm.getStreamMinVolume(streamType) + mAm.getStreamMaxVolume(
- streamType)) / 2;
-
- mAudioService.setStreamVolume(streamType, streamVolume, /*flags=*/0,
+ mAudioService.setStreamVolume(streamType, DEFAULT_STREAM_VOLUME, /*flags=*/0,
mContext.getOpPackageName());
}
- mAudioService.setRingerModeInternal(RINGER_MODE_NORMAL, mContext.getOpPackageName());
- mAudioService.setRingerModeExternal(RINGER_MODE_NORMAL, mContext.getOpPackageName());
+ if (!mIsAutomotive) {
+ mAudioService.setRingerModeInternal(RINGER_MODE_NORMAL, mContext.getOpPackageName());
+ mAudioService.setRingerModeExternal(RINGER_MODE_NORMAL, mContext.getOpPackageName());
+ }
}
private AudioVolumeGroup getStreamTypeVolumeGroup(int streamType) {
@@ -297,6 +302,7 @@ public class VolumeHelperTest {
@Test
public void setStreamRingVolume0_setsRingerModeVibrate() throws Exception {
+ assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
mAudioService.setStreamVolume(STREAM_RING, 0, /*flags=*/0,
mContext.getOpPackageName());
mTestLooper.dispatchAll();
@@ -462,6 +468,7 @@ public class VolumeHelperTest {
@Test
public void flagAllowRingerModes_onSystemStreams_changesMode() throws Exception {
+ assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
mAudioService.setStreamVolume(STREAM_SYSTEM,
mAudioService.getStreamMinVolume(STREAM_SYSTEM), /*flags=*/0,
mContext.getOpPackageName());
@@ -476,6 +483,7 @@ public class VolumeHelperTest {
@Test
public void flagAllowRingerModesAbsent_onNonSystemStreams_noModeChange() throws Exception {
+ assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
mAudioService.setStreamVolume(STREAM_MUSIC,
mAudioService.getStreamMinVolume(STREAM_MUSIC), /*flags=*/0,
mContext.getOpPackageName());
@@ -544,17 +552,23 @@ public class VolumeHelperTest {
mAudioService.setDeviceVolume(volMin, usbDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
- assertEquals(mAudioService.getDeviceVolume(volMin, usbDevice,
- mContext.getOpPackageName()), volMin);
+ if (!mIsAutomotive) {
+ // there is a min/max index mismatch in automotive
+ assertEquals(mAudioService.getDeviceVolume(volMin, usbDevice,
+ mContext.getOpPackageName()), volMin);
+ }
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+ eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
mAudioService.setDeviceVolume(volMid, usbDevice, mContext.getOpPackageName());
mTestLooper.dispatchAll();
- assertEquals(mAudioService.getDeviceVolume(volMid, usbDevice,
- mContext.getOpPackageName()), volMid);
+ if (!mIsAutomotive) {
+ // there is a min/max index mismatch in automotive
+ assertEquals(mAudioService.getDeviceVolume(volMid, usbDevice,
+ mContext.getOpPackageName()), volMid);
+ }
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
- STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+ eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index f47768280cc1..6ac95c829f56 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -109,6 +109,8 @@ public class FaceEnrollClientTest {
private AidlResponseHandler mAidlResponseHandler;
@Mock
private AuthenticationStateListeners mAuthenticationStateListeners;
+ @Mock
+ private BiometricUtils<Face> mBiometricUtils;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -213,7 +215,7 @@ public class FaceEnrollClientTest {
mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */,
true /* debugConsent */,
(new FaceEnrollOptions.Builder()).setEnrollReason(ENROLL_SOURCE).build(),
- mAuthenticationStateListeners);
+ mAuthenticationStateListeners, mBiometricUtils);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 6780e60a22b0..bf970867f149 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -50,6 +50,7 @@ import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.UserSwitchProvider;
+import com.android.server.biometrics.sensors.face.FaceUtils;
import org.junit.Before;
import org.junit.Test;
@@ -90,6 +91,8 @@ public class SensorTest {
private AidlSession mCurrentSession;
@Mock
private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ private FaceUtils mBiometricUtils;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -114,7 +117,7 @@ public class SensorTest {
mUserSwitchProvider);
mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback, mBiometricUtils);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
index 4248e5e37238..24ce569f644e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -206,7 +206,7 @@ public class HidlToAidlSensorAdapterTest {
new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
false /* debugConsent */, (new FaceEnrollOptions.Builder()).build(),
- mAuthenticationStateListeners));
+ mAuthenticationStateListeners, mBiometricUtils));
mLooper.dispatchAll();
verify(mAidlResponseHandlerCallback).onEnrollSuccess();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 698db2e19661..4ef8782386d5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -51,6 +51,7 @@ import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.UserSwitchProvider;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
@@ -96,6 +97,8 @@ public class SensorTest {
private HandlerThread mThread;
@Mock
AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ private FingerprintUtils mBiometricUtils;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -121,7 +124,7 @@ public class SensorTest {
mUserSwitchProvider);
mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback, mBiometricUtils);
}
@Test
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 51f64ba2b483..b97a2684576c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME;
import static android.app.Notification.COLOR_DEFAULT;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
@@ -36,6 +37,7 @@ import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPIN
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS;
import static com.android.server.notification.GroupHelper.AGGREGATE_GROUP_KEY;
import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
import static com.android.server.notification.GroupHelper.BASE_FLAGS;
@@ -2204,7 +2206,7 @@ public class GroupHelperTest extends UiServiceTestCase {
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(expectedGroupKey_silent), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
- eq(expectedGroupKey_silent), eq(false));
+ eq(expectedGroupKey_silent), eq(true));
// Check that the alerting section group is removed
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2264,13 +2266,15 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList);
// Check that channel1's notifications are moved to the silent section group
- expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
- mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
- "TEST_CHANNEL_ID1");
- verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- eq(expectedGroupKey_silent), anyInt(), eq(expectedSummaryAttr));
- verify(mCallback, times(AUTOGROUP_AT_COUNT/2 + 1)).addAutoGroup(anyString(),
- eq(expectedGroupKey_silent), eq(false));
+ // But not enough to auto-group => remove override group key
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ for (NotificationRecord record: notificationList) {
+ if (record.getChannel().getId().equals(channel1.getId())) {
+ assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+ }
+ }
// Check that the alerting section group is not removed, only updated
expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
@@ -2343,7 +2347,7 @@ public class GroupHelperTest extends UiServiceTestCase {
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
eq(expectedGroupKey_silent), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(numSilentGroupNotifications)).addAutoGroup(anyString(),
- eq(expectedGroupKey_silent), eq(false));
+ eq(expectedGroupKey_silent), eq(true));
// Check that the alerting section group is removed
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2353,6 +2357,60 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testAutogroup_updateChannel_reachedMinAutogroupCount() {
+ final String pkg = "package";
+ final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+ final NotificationChannel channel2 = new NotificationChannel("TEST_CHANNEL_ID2",
+ "TEST_CHANNEL_ID2", IMPORTANCE_LOW);
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ // Post notifications with different channels that would autogroup in different sections
+ NotificationRecord r;
+ // Not enough notifications to autogroup initially
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ if (i % 2 == 0) {
+ r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, null, false, channel1);
+ } else {
+ r = getNotificationRecord(pkg, i, String.valueOf(i),
+ UserHandle.SYSTEM, null, false, channel2);
+ }
+ notificationList.add(r);
+ mGroupHelper.onNotificationPosted(r, false);
+ }
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ Mockito.reset(mCallback);
+
+ // Update channel1's importance
+ final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ channel1.setImportance(IMPORTANCE_LOW);
+ for (NotificationRecord record: notificationList) {
+ if (record.getChannel().getId().equals(channel1.getId())) {
+ record.updateNotificationChannel(channel1);
+ }
+ }
+ mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
+ notificationList);
+
+ // Check that channel1's notifications are moved to the silent section & autogroup all
+ NotificationAttributes expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
+ mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ "TEST_CHANNEL_ID1");
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_silent), eq(true));
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_silent), anyInt(), eq(expectedSummaryAttr));
+ }
+
+ @Test
@EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
public void testNoGroup_singletonGroup_underLimit() {
@@ -2519,7 +2577,11 @@ public class GroupHelperTest extends UiServiceTestCase {
assertThat(cachedSummary).isNull();
}
- private void checkNonGroupableNotifications() {
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
+ public void testNonGroupableNotifications() {
+ // Check that there is no valid section for: conversations, calls, foreground services
NotificationRecord notification_conversation = mock(NotificationRecord.class);
when(notification_conversation.isConversation()).thenReturn(true);
assertThat(GroupHelper.getSection(notification_conversation)).isNull();
@@ -2592,8 +2654,6 @@ public class GroupHelperTest extends UiServiceTestCase {
"", false, recsChannel);
assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
"AlertingSection");
-
- checkNonGroupableNotifications();
}
@Test
@@ -2638,8 +2698,86 @@ public class GroupHelperTest extends UiServiceTestCase {
"", false, recsChannel);
assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
"RecsSection");
+ }
- checkNonGroupableNotifications();
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+ public void testNonGroupableNotifications_forceGroupConversations() {
+ // Check that there is no valid section for: calls, foreground services
+ NotificationRecord notification_call = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ when(notification_call.isConversation()).thenReturn(false);
+ when(notification_call.getNotification()).thenReturn(n);
+ when(notification_call.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_call)).isNull();
+
+ NotificationRecord notification_colorFg = spy(getNotificationRecord(mPkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+ n = mock(Notification.class);
+ when(notification_colorFg.isConversation()).thenReturn(false);
+ when(notification_colorFg.getNotification()).thenReturn(n);
+ when(notification_colorFg.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isForegroundService()).thenReturn(true);
+ when(n.isColorized()).thenReturn(true);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+ @DisableFlags(FLAG_SORT_SECTION_BY_TIME)
+ public void testConversationGroupSections_disableSortSectionByTime() {
+ // Check that there are separate sections for conversations: alerting and silent
+ NotificationRecord notification_conversation_silent = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_LOW);
+ notification_conversation_silent = spy(notification_conversation_silent);
+ when(notification_conversation_silent.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation_silent).mName).isEqualTo(
+ "PeopleSection(silent)");
+
+ // Check that there is a correct section for conversations
+ NotificationRecord notification_conversation_alerting = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_DEFAULT);
+ notification_conversation_alerting = spy(notification_conversation_alerting);
+ when(notification_conversation_alerting.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation_alerting).mName).isEqualTo(
+ "PeopleSection(alerting)");
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS,
+ FLAG_SORT_SECTION_BY_TIME})
+ public void testConversationGroupSections() {
+ // Check that there is a single section for silent/alerting conversations
+ NotificationRecord notification_conversation_silent = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_LOW);
+ notification_conversation_silent = spy(notification_conversation_silent);
+ when(notification_conversation_silent.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation_silent).mName).isEqualTo(
+ "PeopleSection");
+
+ NotificationRecord notification_conversation_alerting = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_DEFAULT);
+ notification_conversation_alerting = spy(notification_conversation_alerting);
+ when(notification_conversation_alerting.isConversation()).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification_conversation_alerting).mName).isEqualTo(
+ "PeopleSection");
+
+ // Check that there is a section for priority conversations
+ NotificationRecord notification_conversation_prio = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, IMPORTANCE_DEFAULT);
+ notification_conversation_prio = spy(notification_conversation_prio);
+ when(notification_conversation_prio.isConversation()).thenReturn(true);
+ notification_conversation_prio.getChannel().setImportantConversation(true);
+ assertThat(GroupHelper.getSection(notification_conversation_prio).mName).isEqualTo(
+ "PeopleSection(priority)");
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index dd2b84504e51..c1e3f47679ca 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2186,6 +2186,80 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testReadXml_upgradeToModesUi_resetsImplicitRuleIcon() throws Exception {
+ setupZenConfig();
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ ZenRule implicitRuleWithModesUi = expectedImplicitRule("pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, POLICY, null);
+
+ // Add one implicit rule in the pre-MODES_UI configuration.
+ ZenRule implicitRuleBeforeModesUi = implicitRuleWithModesUi.copy();
+ implicitRuleBeforeModesUi.iconResName = "pkg_icon";
+ mZenModeHelper.mConfig.automaticRules.put(implicitRuleBeforeModesUi.id,
+ implicitRuleBeforeModesUi);
+ // Plus one other normal rule.
+ ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null);
+ anotherRule.id = "other_rule";
+ anotherRule.iconResName = "other_icon";
+ anotherRule.type = TYPE_IMMERSIVE;
+ mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule);
+
+ // Write with pre-modes-ui = (modes_api) version, then re-read.
+ ByteArrayOutputStream baos = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_API);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ // Implicit rule was updated.
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleBeforeModesUi.id))
+ .isEqualTo(implicitRuleWithModesUi);
+
+ // The other rule was untouched.
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(anotherRule.id))
+ .isEqualTo(anotherRule);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void testReadXml_onModesUi_implicitRulesUntouched() throws Exception {
+ setupZenConfig();
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ // Add one implicit rule already in its post-modes-UI configuration, also customized with
+ // an icon;
+ ZenRule implicitRuleWithModesUi = expectedImplicitRule("pkg",
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS, POLICY, null);
+ implicitRuleWithModesUi.iconResName = "icon_chosen_by_user";
+ mZenModeHelper.mConfig.automaticRules.put(implicitRuleWithModesUi.id,
+ implicitRuleWithModesUi);
+
+ // Plus one other normal rule.
+ ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null);
+ anotherRule.id = "other_rule";
+ anotherRule.iconResName = "other_icon";
+ anotherRule.type = TYPE_IMMERSIVE;
+ mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule);
+
+ // Write with modes_ui version, then re-read.
+ ByteArrayOutputStream baos = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ // Both rules were untouched
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleWithModesUi.id))
+ .isEqualTo(implicitRuleWithModesUi);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(anotherRule.id))
+ .isEqualTo(anotherRule);
+ }
+
+ @Test
public void testCountdownConditionSubscription() throws Exception {
ZenModeConfig config = new ZenModeConfig();
mZenModeHelper.mConfig = config;
@@ -5538,6 +5612,49 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void removeAndAddAutomaticZenRule_ifChangingComponent_isAllowedAndDoesNotRestore() {
+ // Start with a rule.
+ mZenModeHelper.mConfig.automaticRules.clear();
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setOwner(new ComponentName("first", "owner"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+
+ // User customizes it.
+ AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+ "userUpdate", SYSTEM_UID);
+
+ // App deletes it. It's preserved for a possible restoration.
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+ // App adds it again, but this time with a different owner!
+ AutomaticZenRule readdingWithDifferentOwner = new AutomaticZenRule.Builder(rule)
+ .setOwner(new ComponentName("second", "owner"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .build();
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ readdingWithDifferentOwner, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+ // Verify that the rule was NOT restored:
+ assertThat(newRuleId).isNotEqualTo(ruleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+ assertThat(finalRule.getOwner()).isEqualTo(new ComponentName("second", "owner"));
+
+ // Also, we discarded the "deleted rule" since we found it but decided not to use it.
+ assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+ }
+
+ @Test
@EnableFlags(FLAG_MODES_API)
public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() {
mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
@@ -6809,7 +6926,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule.zenPolicy = policy;
rule.pkg = ownerPkg;
rule.name = CUSTOM_APP_LABEL;
- rule.iconResName = ICON_RES_NAME;
+ if (!Flags.modesUi()) {
+ rule.iconResName = ICON_RES_NAME;
+ }
rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
CUSTOM_APP_LABEL);
rule.type = AutomaticZenRule.TYPE_OTHER;
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 43ad44f057cc..2549ff5360ec 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -32,11 +32,11 @@ android_test {
"frameworks-base-testutils",
"frameworks-services-vibrator-testutils",
"junit",
- "junit-params",
"mockito-target-inline-minus-junit4",
"platform-test-annotations",
"service-permission.stubs.system_server",
"services.core",
+ "TestParameterInjector",
],
jni_libs: ["libdexmakerjvmtiagent"],
platform_apis: true,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index e0d05df1de80..2d312d2649dd 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -18,32 +18,36 @@ package com.android.server.vibrator;
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_CLICK;
+import static android.os.VibrationEffect.EFFECT_TICK;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED;
+import static android.os.vibrator.Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES;
import static com.android.internal.R.xml.haptic_feedback_customization;
-import static com.android.server.vibrator.HapticFeedbackCustomization.CustomizationParserException;
+import static com.android.internal.R.xml.haptic_feedback_customization_source_rotary_encoder;
+import static com.android.internal.R.xml.haptic_feedback_customization_source_touchscreen;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
-import android.os.vibrator.Flags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
-import android.util.SparseArray;
+import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
-import com.android.internal.annotations.Keep;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import org.junit.Before;
import org.junit.Rule;
@@ -56,7 +60,7 @@ import org.mockito.junit.MockitoRule;
import java.io.File;
import java.io.FileOutputStream;
-@RunWith(JUnitParamsRunner.class)
+@RunWith(TestParameterInjector.class)
public class HapticFeedbackCustomizationTest {
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -69,83 +73,78 @@ public class HapticFeedbackCustomizationTest {
private static final VibrationEffect COMPOSITION_VIBRATION =
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
- private static final String PREDEFINED_VIBRATION_XML =
+ private static final String PREDEFINED_VIBRATION_CLICK_XML =
"<vibration-effect><predefined-effect name=\"click\"/></vibration-effect>";
- private static final VibrationEffect PREDEFINED_VIBRATION =
+ private static final VibrationEffect PREDEFINED_VIBRATION_CLICK =
VibrationEffect.createPredefined(EFFECT_CLICK);
+ private static final String PREDEFINED_VIBRATION_TICK_XML =
+ "<vibration-effect><predefined-effect name=\"tick\"/></vibration-effect>";
+ private static final VibrationEffect PREDEFINED_VIBRATION_TICK =
+ VibrationEffect.createPredefined(EFFECT_TICK);
+
private static final String WAVEFORM_VIBRATION_XML = "<vibration-effect>"
+ "<waveform-effect>"
+ "<waveform-entry durationMs=\"123\" amplitude=\"254\"/>"
+ "</waveform-effect>"
+ "</vibration-effect>";
- private static final VibrationEffect WAVEFORM_VIBARTION =
+ private static final VibrationEffect WAVEFORM_VIBRATION =
VibrationEffect.createWaveform(new long[] {123}, new int[] {254}, -1);
@Mock private Resources mResourcesMock;
@Mock private VibratorInfo mVibratorInfoMock;
- @Keep
- private static Object[][] hapticFeedbackCustomizationTestArguments() {
- // (boolean hasConfigFile, boolean hasRes).
- return new Object[][] {{true, true}, {true, false}, {false, true}};
+ private enum CustomizationSource {
+ DEVICE_CONFIG_FILE,
+ DEVICE_RESOURCE,
+ DEVICE_RESOURCE_INPUT_ROTARY,
+ DEVICE_RESOURCE_INPUT_TOUCHSCREEN
}
@Before
public void setUp() {
+ clearFileAndResourceSetup();
when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
- mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
- mSetFlagsRule.disableFlags(
- Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_noCustomization_success(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xml = "<haptic-feedback-constants></haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- setupParseCustomizations(xml, hasConfigFile, hasRes);
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_featureFlagDisabled_returnsNull(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+ public void testParseCustomizations_featureFlagDisabled_customizationNotLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
// Valid customization XML.
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- setupParseCustomizations(xml, hasConfigFile, hasRes);
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
.isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
public void testParseCustomizations_oneVibrationCustomization_success(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ @TestParameter CustomizationSource customizationSource)
+ throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- expectedMapping.put(10, COMPOSITION_VIBRATION);
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+ .isEqualTo(COMPOSITION_VIBRATION);
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
public void testParseCustomizations_oneVibrationSelectCustomization_success(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ @TestParameter CustomizationSource customizationSource)
+ throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
@@ -153,28 +152,28 @@ public class HapticFeedbackCustomizationTest {
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- expectedMapping.put(10, COMPOSITION_VIBRATION);
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+ .isEqualTo(COMPOSITION_VIBRATION);
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
public void testParseCustomizations_multipleCustomizations_success(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ @TestParameter CustomizationSource customizationSource) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "<constant id=\"12\">"
+ "<vibration-select>"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ WAVEFORM_VIBRATION_XML
+ "</vibration-select>"
+ "</constant>"
+ "<constant id=\"150\">"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ "</constant>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
@@ -183,33 +182,39 @@ public class HapticFeedbackCustomizationTest {
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- expectedMapping.put(1, COMPOSITION_VIBRATION);
- expectedMapping.put(12, PREDEFINED_VIBRATION);
- expectedMapping.put(150, PREDEFINED_VIBRATION);
- expectedMapping.put(10, WAVEFORM_VIBARTION);
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+ customization))
+ .isEqualTo(COMPOSITION_VIBRATION);
+ assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+ customization))
+ .isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+ customization))
+ .isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customization))
+ .isEqualTo(WAVEFORM_VIBRATION);
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success(
- boolean hasConfigFile, boolean hasRes)
- throws Exception {
- makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION_CLICK, WAVEFORM_VIBRATION);
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "<constant id=\"12\">"
+ "<vibration-select>"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ WAVEFORM_VIBRATION_XML
+ "</vibration-select>"
+ "</constant>"
+ "<constant id=\"150\">"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ "</constant>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
@@ -218,17 +223,23 @@ public class HapticFeedbackCustomizationTest {
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+ customization)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+ customization)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+ customization)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customization)).isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
- boolean hasConfigFile, boolean hasRes)
- throws Exception {
- makeSupported(PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ makeSupported(PREDEFINED_VIBRATION_CLICK, WAVEFORM_VIBRATION);
makeUnsupported(COMPOSITION_VIBRATION);
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">" // No supported customization.
@@ -236,68 +247,89 @@ public class HapticFeedbackCustomizationTest {
+ "</constant>"
+ "<constant id=\"12\">" // PREDEFINED_VIBRATION is the first/only supported.
+ "<vibration-select>"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ COMPOSITION_VIBRATION_XML
+ "</vibration-select>"
+ "</constant>"
- + "<constant id=\"14\">" // WAVEFORM_VIBARTION is the first/only supported.
+ + "<constant id=\"14\">" // WAVEFORM_VIBRATION is the first/only supported.
+ "<vibration-select>"
+ COMPOSITION_VIBRATION_XML
+ WAVEFORM_VIBRATION_XML
+ "</vibration-select>"
+ "</constant>"
+ "<constant id=\"150\">" // PREDEFINED_VIBRATION is the first/only supported.
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ "</constant>"
+ "<constant id=\"10\">" // PREDEFINED_VIBRATION is the first supported.
+ "<vibration-select>"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ WAVEFORM_VIBRATION_XML
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- expectedMapping.put(12, PREDEFINED_VIBRATION);
- expectedMapping.put(14, WAVEFORM_VIBARTION);
- expectedMapping.put(150, PREDEFINED_VIBRATION);
- expectedMapping.put(10, PREDEFINED_VIBRATION);
-
- assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+ customization)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+ customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(getEffectForSource(/* effectId= */ 14, customizationSource,
+ customization)).isEqualTo(WAVEFORM_VIBRATION);
+ assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+ customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
}
@Test
- public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception {
- setCustomizationFilePath("");
-
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
- .isNull();
-
- setCustomizationFilePath(null);
-
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
- .isNull();
-
- setCustomizationFilePath("non_existent_file.xml");
-
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
- .isNull();
- }
-
- @Test
- public void testParseCustomizations_noCustomizationResource_returnsNull() throws Exception {
- mSetFlagsRule.enableFlags(
- Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
- doThrow(new Resources.NotFoundException())
- .when(mResourcesMock).getXml(haptic_feedback_customization);
-
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
- .isNull();
+ public void testParseCustomizations_malformedXml_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ // No end "<constant>" tag
+ String xmlNoEndConstantTag = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customizationNoEndConstantTag = createCustomizationForSource(
+ xmlNoEndConstantTag, customizationSource);
+ // No start "<haptic-feedback-constants>" tag
+ String xmlNoStartCustomizationTag = "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationNoStartCustomizationTag =
+ createCustomizationForSource(xmlNoStartCustomizationTag, customizationSource);
+ // No end "<haptic-feedback-constants>" tag
+ String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationNoEndCustomizationTag =
+ createCustomizationForSource(xmlNoEndCustomizationTag, customizationSource);
+ // No start "<constant>" tag
+ String xmlNoStartConstantTag = "<haptic-feedback-constants>"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationNoStartConstantTag = createCustomizationForSource(
+ xmlNoStartConstantTag, customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationNoEndConstantTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationNoStartCustomizationTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationNoEndCustomizationTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationNoStartConstantTag)).isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ public void testParseCustomizations_disallowedVibrationForHapticFeedback_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
// The XML content is good, but the serialized vibration is not supported for haptic
// feedback usage (i.e. repeating vibration).
String xml = "<haptic-feedback-constants>"
@@ -311,185 +343,245 @@ public class HapticFeedbackCustomizationTest {
+ "</vibration-effect>"
+ "</constant>"
+ "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
- }
-
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_emptyXml_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- assertParseCustomizationsFails("", hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+ .isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_noVibrationXml_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ public void testParseCustomizations_xmlNoVibration_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ "</constant>"
+ "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+ customizationSource);
- assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 1, customizationSource, customization))
+ .isNull();
}
+
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_badEffectId_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- // Negative id
+ public void testParseCustomizations_badEffectId_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
String xmlNegativeId = "<haptic-feedback-constants>"
+ "<constant id=\"-10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
- // Non-numeral id
- String xmlNonNumericalId = "<haptic-feedback-constants>"
- + "<constant id=\"xyz\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "</haptic-feedback-constants>";
-
- assertParseCustomizationsFails(xmlNegativeId, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlNonNumericalId, hasConfigFile, hasRes);
- }
+ HapticFeedbackCustomization customization = createCustomizationForSource(
+ xmlNegativeId, customizationSource);
- @Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_malformedXml_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- // No start "<constant>" tag
- String xmlNoStartConstantTag = "<haptic-feedback-constants>"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "</haptic-feedback-constants>";
- // No end "<constant>" tag
- String xmlNoEndConstantTag = "<haptic-feedback-constants>"
- + "<constant id=\"10\">"
- + COMPOSITION_VIBRATION_XML
- + "</haptic-feedback-constants>";
- // No start "<haptic-feedback-constants>" tag
- String xmlNoStartCustomizationTag = "<constant id=\"10\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>"
- + "</haptic-feedback-constants>";
- // No end "<haptic-feedback-constants>" tag
- String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
- + "<constant id=\"10\">"
- + COMPOSITION_VIBRATION_XML
- + "</constant>";
-
- assertParseCustomizationsFails(xmlNoStartConstantTag, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlNoEndConstantTag, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlNoStartCustomizationTag, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlNoEndCustomizationTag, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ -10, customizationSource, customization))
+ .isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_badVibrationXml_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xmlBad1 = "<haptic-feedback-constants>"
+ public void testParseCustomizations_badVibrationXml_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ // Case#1 - bad opening tag <bad-vibration-effect>
+ String xmlBadTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<bad-vibration-effect></bad-vibration-effect>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- String xmlBad2 = "<haptic-feedback-constants>"
+ HapticFeedbackCustomization customizationBadTag = createCustomizationForSource(
+ xmlBadTag, customizationSource);
+ // Case#2 - bad attribute "name" for tag <predefined-effect>
+ String xmlBadEffectName = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- String xmlBad3 = "<haptic-feedback-constants>"
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationBadEffectName = createCustomizationForSource(
+ xmlBadEffectName, customizationSource);
+ // Case#3 - miss "</vibration-select>"
+ String xmlBadEffectNameAndMissingCloseTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</constant>"
+ "</haptic-feedback-constants>";
- String xmlBad4 = "<haptic-feedback-constants>"
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationBadEffectNameAndMissingCloseTag =
+ createCustomizationForSource(xmlBadEffectNameAndMissingCloseTag,
+ customizationSource);
+ // Case#4 - miss "<vibration-select>"
+ String xmlBadEffectNameAndMissingOpenTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
-
- assertParseCustomizationsFails(xmlBad1, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlBad2, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlBad3, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlBad4, hasConfigFile, hasRes);
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationBadEffectNameAndMissingOpenTag =
+ createCustomizationForSource(xmlBadEffectNameAndMissingOpenTag,
+ customizationSource);
+
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadEffectName)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadEffectNameAndMissingCloseTag)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadEffectNameAndMissingOpenTag)).isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_badConstantAttribute_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
- String xmlBadConstantAttribute1 = "<haptic-feedback-constants>"
+ public void testParseCustomizations_badConstantAttribute_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
+ // Case#1 - bad attribute id for tag <constant>
+ String xmlBadConstantIdAttribute = "<haptic-feedback-constants>"
+ "<constant iddddd=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
- String xmlBadConstantAttribute2 = "<haptic-feedback-constants>"
+ HapticFeedbackCustomization customizationBadConstantIdAttribute =
+ createCustomizationForSource(xmlBadConstantIdAttribute, customizationSource);
+ // Case#2 - unexpected attribute "unwanted" for tag <constant>
+ String xmlUnwantedConstantAttribute = "<haptic-feedback-constants>"
+ "<constant id=\"10\" unwanted-attr=\"1\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
+ clearFileAndResourceSetup();
+ HapticFeedbackCustomization customizationUnwantedConstantAttribute =
+ createCustomizationForSource(xmlUnwantedConstantAttribute, customizationSource);
- assertParseCustomizationsFails(xmlBadConstantAttribute1, hasConfigFile, hasRes);
- assertParseCustomizationsFails(xmlBadConstantAttribute2, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationBadConstantIdAttribute)).isNull();
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+ customizationUnwantedConstantAttribute)).isNull();
}
@Test
- @Parameters(method = "hapticFeedbackCustomizationTestArguments")
- public void testParseCustomizations_duplicateEffects_throwsException(
- boolean hasConfigFile, boolean hasRes) throws Exception {
+ public void testParseCustomizations_duplicateEffects_notLoaded(
+ @TestParameter CustomizationSource customizationSource) throws Exception {
String xmlDuplicateEffect = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "<constant id=\"10\">"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ "</constant>"
+ "<constant id=\"11\">"
- + PREDEFINED_VIBRATION_XML
+ + PREDEFINED_VIBRATION_CLICK_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
+ HapticFeedbackCustomization customization = createCustomizationForSource(xmlDuplicateEffect,
+ customizationSource);
- assertParseCustomizationsFails(xmlDuplicateEffect, hasConfigFile, hasRes);
+ assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+ .isNull();
+ assertThat(getEffectForSource(/* effectId= */ 11, customizationSource, customization))
+ .isNull();
}
- private void assertParseCustomizationsSucceeds(String xml,
- SparseArray<VibrationEffect> expectedCustomizations, boolean hasConfigFile,
- boolean hasRes) throws Exception {
- setupParseCustomizations(xml, hasConfigFile, hasRes);
- assertThat(expectedCustomizations.contentEquals(
- HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
- .isTrue();
+ @Test
+ public void testParseCustomizations_withDifferentCustomizations_loadsCorrectOne()
+ throws Exception {
+ String xmlBaseCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "<constant id=\"14\">"
+ + "<vibration-select>"
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ String xmlRotaryInputCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_CLICK_XML
+ + COMPOSITION_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ String xmlTouchScreenInputCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + PREDEFINED_VIBRATION_TICK_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ setupCustomizations(xmlBaseCustomization, CustomizationSource.DEVICE_RESOURCE);
+ setupCustomizations(xmlRotaryInputCustomization,
+ CustomizationSource.DEVICE_RESOURCE_INPUT_ROTARY);
+ HapticFeedbackCustomization customization = createCustomizationForSource(
+ xmlTouchScreenInputCustomization,
+ CustomizationSource.DEVICE_RESOURCE_INPUT_TOUCHSCREEN);
+
+ // Matching customizations.
+ assertThat(customization.getEffect(/* effectId= */ 10)).isEqualTo(COMPOSITION_VIBRATION);
+ assertThat(customization.getEffect(/* effectId= */ 14)).isEqualTo(WAVEFORM_VIBRATION);
+ assertThat(customization.getEffect(/* effectId= */ 10,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+ assertThat(customization.getEffect(/* effectId= */ 10,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PREDEFINED_VIBRATION_TICK);
+ // Missing from input source customization xml. Fallback to base.
+ assertThat(customization.getEffect(/* effectId= */ 14,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(WAVEFORM_VIBRATION);
+ assertThat(customization.getEffect(/* effectId= */ 14,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(WAVEFORM_VIBRATION);
}
- private void assertParseCustomizationsFails(String xml, boolean hasConfigFile, boolean hasRes)
+ @Test
+ public void testParseCustomizations_customizationsFromConfigFileAndRes_preferConfigFile()
throws Exception {
- setupParseCustomizations(xml, hasConfigFile, hasRes);
- assertThrows("Expected haptic feedback customization to fail",
- CustomizationParserException.class,
- () -> HapticFeedbackCustomization.loadVibrations(
- mResourcesMock, mVibratorInfoMock));
+ String xmlConfigFileCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ String xmlResourceCustomization = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + "<vibration-select>"
+ + PREDEFINED_VIBRATION_CLICK_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "<constant id=\"14\">"
+ + "<vibration-select>"
+ + WAVEFORM_VIBRATION_XML
+ + "</vibration-select>"
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ setupCustomizations(xmlConfigFileCustomization, CustomizationSource.DEVICE_CONFIG_FILE);
+ HapticFeedbackCustomization customization = createCustomizationForSource(
+ xmlResourceCustomization, CustomizationSource.DEVICE_RESOURCE);
+
+ // When config file and resource customizations are both available. Load the config file
+ // Customization.
+ assertThat(customization.getEffect(/* effectId= */ 10)).isEqualTo(COMPOSITION_VIBRATION);
+ assertThat(customization.getEffect(/* effectId= */ 14)).isNull();
}
- private void setupParseCustomizations(String xml, boolean hasConfigFile, boolean hasRes)
+ private HapticFeedbackCustomization createCustomizationForSource(String xml,
+ CustomizationSource customizationSource) throws Exception {
+ setupCustomizations(xml, customizationSource);
+ return new HapticFeedbackCustomization(mResourcesMock, mVibratorInfoMock);
+ }
+
+ private void setupCustomizations(String xml, CustomizationSource customizationSource)
throws Exception {
- clearFileAndResourceSetup();
- if (hasConfigFile) {
- setupCustomizationFile(xml);
- }
- if (hasRes) {
- setupCustomizationResource(xml);
+ switch (customizationSource) {
+ case DEVICE_CONFIG_FILE -> setupCustomizationFile(xml);
+ case DEVICE_RESOURCE -> setupCustomizationResource(xml, haptic_feedback_customization);
+ case DEVICE_RESOURCE_INPUT_ROTARY -> setupCustomizationResource(xml,
+ haptic_feedback_customization_source_rotary_encoder);
+ case DEVICE_RESOURCE_INPUT_TOUCHSCREEN -> setupCustomizationResource(xml,
+ haptic_feedback_customization_source_touchscreen);
}
}
- private void clearFileAndResourceSetup() {
- when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
- .thenReturn(null);
- when(mResourcesMock.getXml(haptic_feedback_customization)).thenReturn(null);
+ private void setupCustomizationResource(String xml, int xmlResId) throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ mSetFlagsRule.enableFlags(FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+ doReturn(FakeXmlResourceParser.fromXml(xml)).when(mResourcesMock).getXml(xmlResId);
}
private void setupCustomizationFile(String xml) throws Exception {
@@ -498,15 +590,34 @@ public class HapticFeedbackCustomizationTest {
}
private void setCustomizationFilePath(String path) {
- when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
- .thenReturn(path);
+ doReturn(path).when(mResourcesMock)
+ .getString(R.string.config_hapticFeedbackCustomizationFile);
+ }
+
+ private void clearFileAndResourceSetup() {
+ doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+ .getString(R.string.config_hapticFeedbackCustomizationFile);
+ doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+ .getXml(haptic_feedback_customization);
+ doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+ .getXml(haptic_feedback_customization_source_rotary_encoder);
+ doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+ .getXml(haptic_feedback_customization_source_touchscreen);
}
- private void setupCustomizationResource(String xml) throws Exception {
- mSetFlagsRule.enableFlags(
- Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
- when(mResourcesMock.getXml(haptic_feedback_customization))
- .thenReturn(FakeXmlResourceParser.fromXml(xml));
+ @Nullable
+ private VibrationEffect getEffectForSource(int effectId,
+ CustomizationSource customizationSource,
+ HapticFeedbackCustomization hapticFeedbackCustomization) {
+ return switch (customizationSource) {
+ case DEVICE_CONFIG_FILE, DEVICE_RESOURCE -> hapticFeedbackCustomization.getEffect(
+ effectId);
+ case DEVICE_RESOURCE_INPUT_ROTARY -> hapticFeedbackCustomization.getEffect(effectId,
+ InputDevice.SOURCE_ROTARY_ENCODER);
+ case DEVICE_RESOURCE_INPUT_TOUCHSCREEN -> hapticFeedbackCustomization.getEffect(
+ effectId,
+ InputDevice.SOURCE_TOUCHSCREEN);
+ };
}
private void makeSupported(VibrationEffect... effects) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 8797e63dab27..6076d3318c40 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -21,16 +21,21 @@ import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSIT
import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_QUICK_RISE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_THUD;
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_CLICK;
import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
import static android.os.VibrationEffect.EFFECT_TICK;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
import static android.view.HapticFeedbackConstants.BIOMETRIC_CONFIRM;
import static android.view.HapticFeedbackConstants.BIOMETRIC_REJECT;
import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+import static android.view.HapticFeedbackConstants.DRAG_START;
import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE;
import static android.view.HapticFeedbackConstants.KEYBOARD_TAP;
+import static android.view.HapticFeedbackConstants.NO_HAPTICS;
import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED;
import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
@@ -42,6 +47,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.vibrator.IVibrator;
@@ -52,11 +58,13 @@ import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -75,6 +83,11 @@ public class HapticFeedbackVibrationProviderTest {
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();
+ private static final VibrationEffect PRIMITIVE_THUD_EFFECT =
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_THUD, 0.5497f).compose();
+ private static final VibrationEffect PRIMITIVE_QUICK_RISE_EFFECT =
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_QUICK_RISE,
+ 0.6497f).compose();
private static final int[] SCROLL_FEEDBACK_CONSTANTS =
new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK};
@@ -90,45 +103,52 @@ public class HapticFeedbackVibrationProviderTest {
@Mock private Resources mResourcesMock;
- @Test
- public void testNonExistentCustomization_useDefault() throws Exception {
- // No customization file is set.
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
- .isEqualTo(VibrationEffect.get(EFFECT_TICK));
-
- // The customization file specifies no customization.
- setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>");
- hapticProvider = createProviderWithDefaultCustomizations();
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
- .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+ @Before
+ public void setUp() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
}
@Test
- public void testExceptionParsingCustomizations_useDefault() throws Exception {
- setupCustomizationFile("<bad-xml></bad-xml>");
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
- .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+ public void testNonExistentCustomization_useDefault() {
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ // No customization for `CLOCK_TICK`, so the default vibration is used.
+ assertThat(provider.getVibration(CLOCK_TICK)).isEqualTo(
+ VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(CLOCK_TICK,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+ VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(CLOCK_TICK, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
}
@Test
- public void testUseValidCustomizedVibration() throws Exception {
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+ public void testUseValidCustomizedVibration() {
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD,
+ PRIMITIVE_QUICK_RISE);
SparseArray<VibrationEffect> customizations = new SparseArray<>();
customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
-
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
-
- // The override for `CONTEXT_CLICK` is used.
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+ SparseArray<VibrationEffect> customizationsRotary = new SparseArray<>();
+ customizationsRotary.put(CONTEXT_CLICK, PRIMITIVE_TICK_EFFECT);
+ customizationsRotary.put(DRAG_START, PRIMITIVE_QUICK_RISE_EFFECT);
+ SparseArray<VibrationEffect> customizationsTouchScreen = new SparseArray<>();
+ customizationsTouchScreen.put(CONTEXT_CLICK, PRIMITIVE_THUD_EFFECT);
+ customizationsTouchScreen.put(DRAG_START, PRIMITIVE_CLICK_EFFECT);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations,
+ customizationsRotary, customizationsTouchScreen);
+
+ // The customization for `CONTEXT_CLICK`.
+ assertThat(provider.getVibration(CONTEXT_CLICK))
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
- // `CLOCK_TICK` has no override, so the default vibration is used.
- assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
- .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(CONTEXT_CLICK,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_TICK_EFFECT);
+ assertThat(provider.getVibration(CONTEXT_CLICK,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PRIMITIVE_THUD_EFFECT);
+ // The customization for `DRAG_START`.
+ assertThat(provider.getVibration(DRAG_START,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+ assertThat(provider.getVibration(DRAG_START,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
}
@Test
@@ -140,92 +160,151 @@ public class HapticFeedbackVibrationProviderTest {
+ "</constant>"
+ "</haptic-feedback-constants>";
setupCustomizationFile(xml);
-
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
// The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
- assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+ assertThat(provider.getVibration(CONTEXT_CLICK))
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
// `CLOCK_TICK` has no override, so the default vibration is used.
- assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
+ assertThat(provider.getVibration(CLOCK_TICK))
.isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
}
@Test
- public void testHapticTextDisabled_noVibrationReturnedForTextHandleMove() throws Exception {
+ public void testHapticTextDisabled_noVibrationReturnedForTextHandleMove() {
mockHapticTextSupport(false);
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
SparseArray<VibrationEffect> customizations = new SparseArray<>();
customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
+ HapticFeedbackVibrationProvider provider = createProvider(
+ /* customizations= */ customizations,
+ /* customizationsForRotary= */ customizations,
+ /* customizationsForTouchScreen= */ customizations);
// Test with a customization available for `TEXT_HANDLE_MOVE`.
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isNull();
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+ assertThat(
+ provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
// Test with no customization available for `TEXT_HANDLE_MOVE`.
- hapticProvider = createProvider(/* customizations= */ null);
+ provider = createProviderWithoutCustomizations();
- assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isNull();
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+ assertThat(
+ provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
}
@Test
- public void testHapticTextEnabled_vibrationReturnedForTextHandleMove() throws Exception {
+ public void testHapticTextEnabled_vibrationReturnedForTextHandleMove() {
mockHapticTextSupport(true);
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_THUD, PRIMITIVE_TICK);
SparseArray<VibrationEffect> customizations = new SparseArray<>();
customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
-
+ SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+ customizationsByRotary.put(TEXT_HANDLE_MOVE, PRIMITIVE_TICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+ customizationsByTouchScreen.put(TEXT_HANDLE_MOVE, PRIMITIVE_THUD_EFFECT);
// Test with a customization available for `TEXT_HANDLE_MOVE`.
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations,
+ customizationsByRotary, customizationsByTouchScreen);
- assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
- .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_TICK_EFFECT);
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
// Test with no customization available for `TEXT_HANDLE_MOVE`.
- hapticProvider = createProvider(/* customizations= */ null);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
+ provider = createProviderWithoutCustomizations();
+
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isEqualTo(
+ VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+ VibrationEffect.get(EFFECT_TEXTURE_TICK));
+ assertThat(provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN))
.isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
}
@Test
- public void testValidCustomizationPresentForSafeModeEnabled_usedRegardlessOfVibrationResource()
- throws Exception {
- mockSafeModeEnabledVibration(10, 20, 30, 40);
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+ public void testFeedbackConstantNoHapticEffect_noVibrationRegardlessCustomizations() {
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_THUD, PRIMITIVE_TICK);
SparseArray<VibrationEffect> customizations = new SparseArray<>();
- customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
-
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+ customizations.put(NO_HAPTICS, PRIMITIVE_CLICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+ customizationsByRotary.put(NO_HAPTICS, PRIMITIVE_TICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+ customizationsByTouchScreen.put(NO_HAPTICS, PRIMITIVE_THUD_EFFECT);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations,
+ customizationsByRotary, customizationsByTouchScreen);
+
+ // Whatever customization set to NO_HAPTICS, no vibration happens.
+ assertThat(provider.getVibration(NO_HAPTICS)).isNull();
+ assertThat(provider.getVibration(NO_HAPTICS, InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+ assertThat(provider.getVibration(NO_HAPTICS, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
+ }
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
- .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ @Test
+ public void testValidCustomizationPresentForSafeModeEnabled_usedRegardlessOfVibrationResource() {
+ mockSafeModeEnabledVibration(10, 20, 30, 40);
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD);
+ SparseArray<VibrationEffect> safeModeCustomizations = new SparseArray<>();
+ safeModeCustomizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
+ SparseArray<VibrationEffect> safeModeCustomizationsByRotary = new SparseArray<>();
+ safeModeCustomizationsByRotary.put(SAFE_MODE_ENABLED, PRIMITIVE_THUD_EFFECT);
+ SparseArray<VibrationEffect> safeModeCustomizationsByTouchScreen = new SparseArray<>();
+ safeModeCustomizationsByTouchScreen.put(SAFE_MODE_ENABLED, PRIMITIVE_TICK_EFFECT);
+ HapticFeedbackVibrationProvider provider =
+ createProvider(safeModeCustomizations, safeModeCustomizationsByRotary,
+ safeModeCustomizationsByTouchScreen);
+
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_TICK_EFFECT);
+ // Resource changed
mockSafeModeEnabledVibration(null);
- hapticProvider = createProvider(customizations);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
- .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ provider =
+ createProvider(safeModeCustomizations, safeModeCustomizationsByRotary,
+ safeModeCustomizationsByTouchScreen);
+
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_TICK_EFFECT);
}
@Test
- public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
- throws Exception {
+ public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed() {
mockSafeModeEnabledVibration(10, 20, 30, 40);
- HapticFeedbackVibrationProvider hapticProvider = createProvider(/* customizations= */ null);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
- .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED))
+ .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
}
@Test
- public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
- throws Exception {
+ public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed() {
mockSafeModeEnabledVibration(null);
- HapticFeedbackVibrationProvider hapticProvider = createProvider(/* customizations= */ null);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isNull();
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isNull();
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isNull();
+ assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+ .isNull();
}
@Test
@@ -236,19 +315,25 @@ public class HapticFeedbackVibrationProviderTest {
customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
// Test with a customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
- HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations);
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
- .isEqualTo(PRIMITIVE_CLICK_EFFECT);
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
- .isEqualTo(PRIMITIVE_TICK_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_TAP)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_RELEASE)).isEqualTo(PRIMITIVE_TICK_EFFECT);
// Test with no customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
- hapticProvider = createProviderWithDefaultCustomizations();
+ provider = createProviderWithoutCustomizations();
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+ assertThat(provider.getVibration(KEYBOARD_TAP))
+ .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+ assertThat(provider.getVibration(KEYBOARD_RELEASE))
+ .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+ assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+ assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+ assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_TOUCHSCREEN))
.isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+ assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_TOUCHSCREEN))
.isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
}
@@ -256,25 +341,64 @@ public class HapticFeedbackVibrationProviderTest {
public void testKeyboardHaptic_fixAmplitude_keyboardVibrationReturned() {
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+ assertThat(provider.getVibration(KEYBOARD_TAP)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_RELEASE)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_TAP,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_RELEASE,
+ InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_TAP,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ assertThat(provider.getVibration(KEYBOARD_RELEASE,
+ InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(
+ VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+ KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+ }
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
- .isEqualTo(VibrationEffect.startComposition()
- .addPrimitive(PRIMITIVE_CLICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
- .compose());
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
- .isEqualTo(VibrationEffect.startComposition()
- .addPrimitive(PRIMITIVE_TICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
- .compose());
+ @Test
+ public void testKeyboardHaptic_withCustomizations_customEffectsUsed() {
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD,
+ PRIMITIVE_QUICK_RISE);
+ SparseArray<VibrationEffect> customizations = new SparseArray<>();
+ customizations.put(KEYBOARD_TAP, PRIMITIVE_CLICK_EFFECT);
+ customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
+ SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+ customizationsByRotary.put(KEYBOARD_TAP, PRIMITIVE_THUD_EFFECT);
+ customizationsByRotary.put(KEYBOARD_RELEASE, PRIMITIVE_QUICK_RISE_EFFECT);
+ SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+ customizationsByTouchScreen.put(KEYBOARD_TAP, PRIMITIVE_QUICK_RISE_EFFECT);
+ customizationsByTouchScreen.put(KEYBOARD_RELEASE, PRIMITIVE_THUD_EFFECT);
+ HapticFeedbackVibrationProvider provider = createProvider(customizations,
+ customizationsByRotary, customizationsByTouchScreen);
+
+ assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_ROTARY_ENCODER))
+ .isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+ assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_TOUCHSCREEN))
+ .isEqualTo(PRIMITIVE_THUD_EFFECT);
}
@Test
public void testVibrationAttribute_biometricConstants_returnsCommunicationRequestUsage() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
}
@@ -282,9 +406,9 @@ public class HapticFeedbackVibrationProviderTest {
@Test
public void testVibrationAttribute_forNotBypassingIntensitySettings() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
SAFE_MODE_ENABLED, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
@@ -292,9 +416,9 @@ public class HapticFeedbackVibrationProviderTest {
@Test
public void testVibrationAttribute_forByassingIntensitySettings() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
SAFE_MODE_ENABLED,
/* flags */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, /* privFlags */ 0);
@@ -304,10 +428,10 @@ public class HapticFeedbackVibrationProviderTest {
@Test
public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() {
mSetFlagsRule.enableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
@@ -317,10 +441,10 @@ public class HapticFeedbackVibrationProviderTest {
@Test
public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() {
mSetFlagsRule.disableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
@@ -329,10 +453,10 @@ public class HapticFeedbackVibrationProviderTest {
@Test
public void testVibrationAttribute_notIme_useTouchUsage() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
@@ -341,10 +465,10 @@ public class HapticFeedbackVibrationProviderTest {
@Test
public void testVibrationAttribute_isIme_useImeFeedbackUsage() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0,
HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId)
@@ -354,20 +478,34 @@ public class HapticFeedbackVibrationProviderTest {
@Test
public void testIsRestricted_biometricConstants_returnsTrue() {
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+ HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
- assertThat(hapticProvider.isRestrictedHapticFeedback(effectId)).isTrue();
+ assertThat(provider.isRestrictedHapticFeedback(effectId)).isTrue();
}
}
- private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() {
- return createProvider(/* customizations= */ null);
+ private HapticFeedbackVibrationProvider createProviderWithoutCustomizations() {
+ return createProvider(/* customizations= */ new SparseArray<>(),
+ /* customizationsRotary= */ new SparseArray<>(),
+ /* customizationsTouchScreen */ new SparseArray<>());
}
private HapticFeedbackVibrationProvider createProvider(
SparseArray<VibrationEffect> customizations) {
- return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
+ return createProvider(customizations, /* customizationsRotary= */ new SparseArray<>(),
+ /* customizationsTouchScreen */ new SparseArray<>());
+ }
+
+ private HapticFeedbackVibrationProvider createProvider(
+ @NonNull SparseArray<VibrationEffect> customizations,
+ @NonNull SparseArray<VibrationEffect> customizationsRotary,
+ @NonNull SparseArray<VibrationEffect> customizationsTouchScreen) {
+ return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo,
+ new HapticFeedbackCustomization(
+ customizations,
+ customizationsRotary,
+ customizationsTouchScreen));
}
private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 40135876303b..4afb56265563 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
@@ -192,6 +194,11 @@ public class VibratorManagerServiceTest {
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private final SparseArray<VibrationEffect> mHapticFeedbackVibrationMap = new SparseArray<>();
+ private final SparseArray<VibrationEffect> mHapticFeedbackVibrationMapSourceRotary =
+ new SparseArray<>();
+ private final SparseArray<VibrationEffect> mHapticFeedbackVibrationMapSourceTouchScreen =
+ new SparseArray<>();
+
private final List<HalVibration> mPendingVibrations = new ArrayList<>();
private VibratorManagerService mService;
@@ -339,8 +346,10 @@ public class VibratorManagerServiceTest {
@Override
HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
Resources resources, VibratorInfo vibratorInfo) {
- return new HapticFeedbackVibrationProvider(
- resources, vibratorInfo, mHapticFeedbackVibrationMap);
+ return new HapticFeedbackVibrationProvider(resources, vibratorInfo,
+ new HapticFeedbackCustomization(mHapticFeedbackVibrationMap,
+ mHapticFeedbackVibrationMapSourceRotary,
+ mHapticFeedbackVibrationMapSourceTouchScreen));
}
@Override
@@ -1475,6 +1484,60 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void performHapticFeedbackForInputDevice_doesNotRequireVibrateOrBypassPermissions()
+ throws Exception {
+ // Deny permissions that would have been required for regular vibrations, and check that
+ // the vibration proceed as expected to verify that haptic feedback does not need these
+ // permissions.
+ denyPermission(android.Manifest.permission.VIBRATE);
+ denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+ denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+ // Flag override to enable the scroll feedback constants to bypass interruption policies.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ mHapticFeedbackVibrationMapSourceRotary.put(
+ HapticFeedbackConstants.SCROLL_TICK,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ mHapticFeedbackVibrationMapSourceTouchScreen.put(
+ HapticFeedbackConstants.DRAG_START,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ HalVibration vibrationByRotary =
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+ HalVibration vibrationByTouchScreen =
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.DRAG_START, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ // 2 haptics: 1 by rotary + 1 by touch screen
+ assertEquals(2, playedSegments.size());
+ // Verify feedback by rotary input
+ PrebakedSegment segmentByRotary = (PrebakedSegment) playedSegments.get(0);
+ assertEquals(VibrationEffect.EFFECT_CLICK, segmentByRotary.getEffectId());
+ VibrationAttributes attrsByRotary = vibrationByRotary.callerInfo.attrs;
+ assertEquals(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, attrsByRotary.getUsage());
+ assertTrue(attrsByRotary.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
+ assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+ // Verify feedback by touch screen input
+ PrebakedSegment segmentByTouchScreen = (PrebakedSegment) playedSegments.get(1);
+ assertEquals(VibrationEffect.EFFECT_TICK, segmentByTouchScreen.getEffectId());
+ VibrationAttributes attrsByTouchScreen = vibrationByTouchScreen.callerInfo.attrs;
+ assertEquals(VibrationAttributes.USAGE_TOUCH, attrsByTouchScreen.getUsage());
+ assertTrue(attrsByRotary.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
+ assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+ }
+
+ @Test
public void performHapticFeedback_restrictedConstantsWithoutPermission_doesNotVibrate()
throws Exception {
// Deny permission to vibrate with restricted constants
@@ -1506,6 +1569,42 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void performHapticFeedbackForInputDevice_restrictedConstantsWithoutPermission_doesNotVibrate()
+ throws Exception {
+ // Deny permission to vibrate with restricted constants
+ denyPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ // Public constant, no permission required
+ mHapticFeedbackVibrationMapSourceRotary.put(
+ HapticFeedbackConstants.CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ // Hidden system-only constant, permission required
+ mHapticFeedbackVibrationMapSourceTouchScreen.put(
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(
+ VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ // This vibrates.
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.CONFIRM, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ false);
+ // This doesn't.
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ false);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ assertEquals(1, playedSegments.size());
+ PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
+ assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
+ }
+
+ @Test
public void performHapticFeedback_restrictedConstantsWithPermission_playsVibration()
throws Exception {
// Grant permission to vibrate with restricted constants
@@ -1539,33 +1638,95 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void performHapticFeedbackForInputDevice_restrictedConstantsWithPermission_playsVibration()
+ throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ // Grant permission to vibrate with restricted constants
+ grantPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
+ // Public constant, no permission required
+ mHapticFeedbackVibrationMapSourceRotary.put(
+ HapticFeedbackConstants.CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ // Hidden system-only constant, permission required
+ mHapticFeedbackVibrationMapSourceTouchScreen.put(
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(
+ VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.CONFIRM, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ false);
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ false);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ assertEquals(2, playedSegments.size());
+ assertEquals(VibrationEffect.EFFECT_CLICK,
+ ((PrebakedSegment) playedSegments.get(0)).getEffectId());
+ assertEquals(VibrationEffect.EFFECT_HEAVY_CLICK,
+ ((PrebakedSegment) playedSegments.get(1)).getEffectId());
+ }
+
+ @Test
public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
denyPermission(android.Manifest.permission.VIBRATE);
mHapticFeedbackVibrationMap.put(
HapticFeedbackConstants.KEYBOARD_TAP,
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ mHapticFeedbackVibrationMapSourceRotary.put(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD));
+ mHapticFeedbackVibrationMapSourceTouchScreen.put(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setVibratorInfoLoadSuccessful(false);
- fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK,
+ VibrationEffect.EFFECT_THUD);
VibratorManagerService service = createService();
+ // performHapticFeedback.
performHapticFeedbackAndWaitUntilFinished(
service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+ // performHapticFeedbackForInputDevice.
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.KEYBOARD_TAP, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.KEYBOARD_TAP, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
assertTrue(fakeVibrator.getAllEffectSegments().isEmpty());
}
@Test
public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
denyPermission(android.Manifest.permission.VIBRATE);
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
// These are bad haptic feedback IDs, so expect no vibration played.
+ // Test performHapticFeedback
performHapticFeedbackAndWaitUntilFinished(service, /* constant= */ -1, /* always= */ false);
performHapticFeedbackAndWaitUntilFinished(
service, HapticFeedbackConstants.NO_HAPTICS, /* always= */ true);
+ // Test performHapticFeedbackForInputDevice
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, /* constant= */ -1, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+ performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, /* constant= */ -1, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
}
@@ -1582,6 +1743,17 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception {
+ VibratorManagerService service = createSystemReadyService();
+
+ HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+
+ assertTrue(vibration.callerToken == service);
+ }
+
+ @Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
// Deny permission to vibrate with vendor effects
@@ -2761,6 +2933,21 @@ public class VibratorManagerServiceTest {
return vib;
}
+ private HalVibration performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+ VibratorManagerService service, int constant, int inputDeviceId, int inputSource,
+ boolean always) throws InterruptedException {
+ HalVibration vib = service.performHapticFeedbackForInputDeviceInternal(UID,
+ Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, constant, inputDeviceId, inputSource,
+ "some reason", service,
+ always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
+ 0 /* privFlags */);
+ if (vib != null) {
+ vib.waitForEnd();
+ }
+
+ return vib;
+ }
+
private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service,
VibrationEffect effect, VibrationAttributes attrs) throws InterruptedException {
return vibrateAndWaitUntilFinished(
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 1423811da0c1..0d8b7208bcac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -705,7 +705,7 @@ public class ActivityRecordTests extends WindowTestsBase {
assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
// Clear size compat.
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
activity.ensureActivityConfiguration();
mDisplayContent.sendNewConfiguration();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index d7cef599358a..a7a08b25fba2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -499,7 +499,7 @@ class AppCompatActivityRobot {
activity.setRequestedOrientation(screenOrientation);
}
// Make sure to use the provided configuration to construct the size compat fields.
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
activity.ensureActivityConfiguration();
// Make sure the display configuration reflects the change of activity.
if (activity.mDisplayContent.updateOrientation()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 99505414b934..b6e393d7be0c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,10 +39,8 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -51,9 +49,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.graphics.Rect;
import android.os.Binder;
@@ -377,41 +373,6 @@ public class AppTransitionTests extends WindowTestsBase {
}
@Test
- public void testDelayWhileRecents() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- doReturn(false).when(dc).onDescendantOrientationChanged(any());
- final Task task = createTask(dc);
-
- // Simulate activity1 launches activity2.
- final ActivityRecord activity1 = createActivityRecord(task);
- activity1.setVisible(true);
- activity1.setVisibleRequested(false);
- activity1.allDrawn = true;
- final ActivityRecord activity2 = createActivityRecord(task);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
- activity2.allDrawn = true;
-
- dc.mClosingApps.add(activity1);
- dc.mOpeningApps.add(activity2);
- dc.prepareAppTransition(TRANSIT_OPEN);
- assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
-
- // Wait until everything in animation handler get executed to prevent the exiting window
- // from being removed during WindowSurfacePlacer Traversal.
- waitUntilHandlersIdle();
-
- // Start recents
- doReturn(true).when(task)
- .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
-
- dc.mAppTransitionController.handleAppTransitionReady();
-
- verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
- verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
- }
-
- @Test
public void testGetAnimationStyleResId() {
// Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
// specifying window type.
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 58e919dda964..f2ea1c972b90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1732,25 +1732,6 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
- @SetupWindows(addWindows = W_ACTIVITY)
- @Test
- public void testRotateSeamlesslyWithFixedRotation() {
- final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
- final ActivityRecord app = mAppWindow.mActivityRecord;
- mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
- mAppWindow.mAttrs.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
-
- // Use seamless rotation if the top app is rotated.
- assertTrue(displayRotation.shouldRotateSeamlessly(ROTATION_0 /* oldRotation */,
- ROTATION_90 /* newRotation */, false /* forceUpdate */));
-
- mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(app);
-
- // Use normal rotation because animating recents is an intermediate state.
- assertFalse(displayRotation.shouldRotateSeamlessly(ROTATION_0 /* oldRotation */,
- ROTATION_90 /* newRotation */, false /* forceUpdate */));
- }
-
@Test
public void testFixedRotationWithPip() {
final DisplayContent displayContent = mDefaultDisplay;
@@ -1828,49 +1809,6 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
- @Test
- public void testRecentsNotRotatingWithFixedRotation() {
- unblockDisplayRotation(mDisplayContent);
- final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
- // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
- final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final ActivityRecord recentsActivity = createActivityRecord(mDisplayContent);
- recentsActivity.setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
- doReturn(mock(RecentsAnimationController.class)).when(mWm).getRecentsAnimationController();
-
- // Do not rotate if the recents animation is animating on top.
- mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
- displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
- assertFalse(displayRotation.updateRotationUnchecked(false));
-
- // Rotation can be updated if the recents animation is finished.
- mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
- assertTrue(displayRotation.updateRotationUnchecked(false));
-
- // Rotation can be updated if the policy is not ok to animate (e.g. going to sleep).
- mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
- displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
- ((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false;
- assertTrue(displayRotation.updateRotationUnchecked(false));
-
- // Rotation can be updated if the recents animation is animating but it is not on top, e.g.
- // switching activities in different orientations by quickstep gesture.
- mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
- mDisplayContent.setFixedRotationLaunchingAppUnchecked(activity);
- displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
- assertTrue(displayRotation.updateRotationUnchecked(false));
-
- // The recents activity should not apply fixed rotation if the top activity is not opaque.
- mDisplayContent.mFocusedApp = activity;
- doReturn(false).when(mDisplayContent.mFocusedApp).occludesParent();
- doReturn(ROTATION_90).when(mDisplayContent).rotationForActivityInDifferentOrientation(
- eq(recentsActivity));
- mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
- assertFalse(recentsActivity.hasFixedRotationTransform());
- }
-
@EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
@Test
public void testRespectNonTopVisibleFixedOrientation() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index d8d5729700ca..ea175a5a52b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -24,6 +24,8 @@ import static org.junit.Assert.assertTrue;
import android.graphics.PixelFormat;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import androidx.test.filters.SmallTest;
@@ -72,6 +74,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
* Checks that scheduling with all the state set and manually triggering the show does succeed.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
@@ -99,6 +102,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
* all the state becomes available.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_noInitialState() {
final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
@@ -126,6 +130,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
* does continue and succeed when the runnable is started.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_delayedAfterPrepareSurfaces() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
@@ -158,6 +163,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
* when the surface placement happens.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_delayedSurfacePlacement() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0dc56f8afc53..964264d82b65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -202,6 +203,11 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().onImeControlTargetChanged(base);
base.setRequestedVisibleTypes(ime(), ime());
getController().onRequestedVisibleTypesChanged(base, null /* statsToken */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // to set the serverVisibility, the IME needs to be drawn and onPostLayout be called.
+ mImeWindow.mWinAnimator.mDrawState = HAS_DRAWN;
+ getController().onPostLayout();
+ }
// Send our spy window (app) into the system so that we can detect the invocation.
final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
@@ -500,6 +506,12 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().onRequestedVisibleTypesChanged(app, null /* statsToken */);
assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // The IME is only set to shown, after onPostLayout is called and all preconditions
+ // (serverVisible, no givenInsetsPending, etc.) are fulfilled
+ getController().getImeSourceProvider().onPostLayout();
+ }
+
getController().updateAboveInsetsState(true /* notifyInsetsChange */);
assertNotNull(app.getInsetsState().peekSource(ID_IME));
verify(app, atLeastOnce()).notifyInsetsChanged();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
deleted file mode 100644
index 63e3e5cf865a..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ /dev/null
@@ -1,834 +0,0 @@
-/*
- * Copyright (C) 2018 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.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-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.verifyNoMoreInteractions;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
-import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
-import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-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.reset;
-
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.platform.test.annotations.Presubmit;
-import android.util.SparseBooleanArray;
-import android.view.IRecentsAnimationRunner;
-import android.view.SurfaceControl;
-import android.view.WindowManager.LayoutParams;
-import android.window.TaskSnapshot;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import com.google.common.truth.Truth;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-
-/**
- * Build/Install/Run:
- * atest WmTests:RecentsAnimationControllerTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class RecentsAnimationControllerTest extends WindowTestsBase {
-
- @Mock SurfaceControl mMockLeash;
- @Mock SurfaceControl.Transaction mMockTransaction;
- @Mock OnAnimationFinishedCallback mFinishedCallback;
- @Mock IRecentsAnimationRunner mMockRunner;
- @Mock RecentsAnimationController.RecentsAnimationCallbacks mAnimationCallbacks;
- @Mock TaskSnapshot mMockTaskSnapshot;
- private RecentsAnimationController mController;
- private Task mRootHomeTask;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- doNothing().when(mWm.mRoot).performSurfacePlacement();
- when(mMockRunner.asBinder()).thenReturn(new Binder());
- mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
- DEFAULT_DISPLAY));
- mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
- assertNotNull(mRootHomeTask);
- }
-
- @Test
- public void testRemovedBeforeStarted_expectCanceled() throws Exception {
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- AnimationAdapter adapter = mController.addAnimation(activity.getTask(),
- false /* isRecentTaskInvisible */);
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
- mFinishedCallback);
-
- // The activity doesn't contain window so the animation target cannot be created.
- mController.startAnimation();
-
- // Verify that the finish callback to reparent the leash is called
- verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter));
- // Verify the animation canceled callback to the app was made
- verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
- verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
- }
-
- @Test
- public void testCancelAfterRemove_expectIgnored() {
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- AnimationAdapter adapter = mController.addAnimation(activity.getTask(),
- false /* isRecentTaskInvisible */);
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
- mFinishedCallback);
-
- // Remove the app window so that the animation target can not be created
- activity.removeImmediately();
- mController.startAnimation();
- mController.cleanupAnimation(REORDER_KEEP_IN_PLACE);
- try {
- mController.cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "test");
- } catch (Exception e) {
- fail("Unexpected failure when canceling animation after finishing it");
- }
- }
-
- @Test
- public void testIncludedApps_expectTargetAndVisible() {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord homeActivity = createHomeActivity();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final ActivityRecord hiddenActivity = createActivityRecord(mDefaultDisplay);
- hiddenActivity.setVisible(false);
- mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
- mDefaultDisplay.getRotation());
- initializeRecentsAnimationController(mController, homeActivity);
-
- // Ensure that we are animating the target activity as well
- assertTrue(mController.isAnimatingTask(homeActivity.getTask()));
- assertTrue(mController.isAnimatingTask(activity.getTask()));
- assertFalse(mController.isAnimatingTask(hiddenActivity.getTask()));
- }
-
- @Test
- public void testLaunchAndStartRecents_expectTargetAndVisible() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord homeActivity = createHomeActivity();
- final Task task = createTask(mDefaultDisplay);
- // Emulate that activity1 has just launched activity2, but app transition has not yet been
- // executed.
- final ActivityRecord activity1 = createActivityRecord(task);
- activity1.setVisible(true);
- activity1.setVisibleRequested(false);
- activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1));
-
- final ActivityRecord activity2 = createActivityRecord(task);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
-
- mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
- mDefaultDisplay.getRotation());
- initializeRecentsAnimationController(mController, homeActivity);
- mController.startAnimation();
- verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */,
- null /* taskSnapshots */);
- }
-
- @Test
- public void testWallpaperIncluded_expectTarget() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord homeActivity = createHomeActivity();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
- spyOn(mDefaultDisplay.mWallpaperController);
- doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
-
- mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
- mDefaultDisplay.getRotation());
- initializeRecentsAnimationController(mController, homeActivity);
- mController.startAnimation();
-
- // Ensure that we are animating the app and wallpaper target
- assertTrue(mController.isAnimatingTask(activity.getTask()));
- assertTrue(mController.isAnimatingWallpaper(wallpaperWindowToken));
- }
-
- @Test
- public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord homeActivity = createHomeActivity();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
- spyOn(mDefaultDisplay.mWallpaperController);
- doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
-
- mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
- mDefaultDisplay.getRotation());
- initializeRecentsAnimationController(mController, homeActivity);
- mController.startAnimation();
-
- // Cancel the animation and ensure the controller is still running
- wallpaperWindowToken.cancelAnimation();
- assertTrue(mController.isAnimatingTask(activity.getTask()));
- assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken));
- verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */,
- null /* taskSnapshots */);
- }
-
- @Test
- public void testFinish_expectTargetAndWallpaperAdaptersRemoved() {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord homeActivity = createHomeActivity();
- final WindowState hwin1 = createWindow(null, TYPE_BASE_APPLICATION, homeActivity, "hwin1");
- homeActivity.addWindow(hwin1);
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
- spyOn(mDefaultDisplay.mWallpaperController);
- doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
-
- // Start and finish the animation
- initializeRecentsAnimationController(mController, homeActivity);
- mController.startAnimation();
-
- assertTrue(mController.isAnimatingTask(homeActivity.getTask()));
- assertTrue(mController.isAnimatingTask(activity.getTask()));
-
- // Reset at this point since we may remove adapters that couldn't be created
- clearInvocations(mController);
- mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
-
- // Ensure that we remove the task (home & app) and wallpaper adapters
- verify(mController, times(2)).removeAnimation(any());
- verify(mController, times(1)).removeWallpaperAnimation(any());
- }
-
- @Test
- public void testDeferCancelAnimation() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
- assertEquals(activity.getTask().getTopVisibleActivity(), activity);
- assertEquals(activity.findMainWindow(), win1);
-
- mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
- assertTrue(mController.isAnimatingTask(activity.getTask()));
-
- mController.setDeferredCancel(true /* deferred */, false /* screenshot */);
- mController.cancelAnimationWithScreenshot(false /* screenshot */);
- verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
-
- // Simulate the app transition finishing
- mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0);
- verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
- }
-
- @Test
- public void testDeferCancelAnimationWithScreenShot() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
- assertEquals(activity.getTask().getTopVisibleActivity(), activity);
- assertEquals(activity.findMainWindow(), win1);
-
- RecentsAnimationController.TaskAnimationAdapter adapter = mController.addAnimation(
- activity.getTask(), false /* isRecentTaskInvisible */);
- assertTrue(mController.isAnimatingTask(activity.getTask()));
-
- spyOn(mWm.mTaskSnapshotController);
- doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
- anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
- mController.setDeferredCancel(true /* deferred */, true /* screenshot */);
- mController.cancelAnimationWithScreenshot(true /* screenshot */);
- verify(mMockRunner).onAnimationCanceled(any(int[].class) /* taskIds */,
- any(TaskSnapshot[].class) /* taskSnapshots */);
-
- // Continue the animation (simulating a call to cleanupScreenshot())
- mController.continueDeferredCancelAnimation();
- verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
- }
-
- @Test
- public void testShouldAnimateWhenNoCancelWithDeferredScreenshot() {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
- assertEquals(activity.getTask().getTopVisibleActivity(), activity);
- assertEquals(activity.findMainWindow(), win1);
-
- mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
- assertTrue(mController.isAnimatingTask(activity.getTask()));
-
- // Assume activity transition should animate when no
- // IRecentsAnimationController#setDeferCancelUntilNextTransition called.
- assertFalse(mController.shouldDeferCancelWithScreenshot());
- assertTrue(activity.shouldAnimate());
- }
-
- @Test
- public void testBinderDiedAfterCancelWithDeferredScreenshot() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord homeActivity = createHomeActivity();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
-
- initializeRecentsAnimationController(mController, homeActivity);
- mController.setWillFinishToHome(true);
-
- // Verify cancel is called with a snapshot and that we've created an overlay
- spyOn(mWm.mTaskSnapshotController);
- doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
- anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
- mController.cancelAnimationWithScreenshot(true /* screenshot */);
- verify(mMockRunner).onAnimationCanceled(any(), any());
-
- // Simulate process crashing and ensure the animation is still canceled
- mController.binderDied();
- verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
- }
-
- @Test
- public void testRecentViewInFixedPortraitWhenTopAppInLandscape() {
- makeDisplayPortrait(mDefaultDisplay);
- unblockDisplayRotation(mDefaultDisplay);
- mWm.setRecentsAnimationController(mController);
-
- final ActivityRecord homeActivity = createHomeActivity();
- homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-
- final ActivityRecord landActivity = createActivityRecord(mDefaultDisplay);
- landActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, landActivity, "win1");
- landActivity.addWindow(win1);
-
- assertEquals(landActivity.getTask().getTopVisibleActivity(), landActivity);
- assertEquals(landActivity.findMainWindow(), win1);
-
- // Ensure that the display is in Landscape
- landActivity.onDescendantOrientationChanged(landActivity);
- assertEquals(Configuration.ORIENTATION_LANDSCAPE,
- mDefaultDisplay.getConfiguration().orientation);
-
- initializeRecentsAnimationController(mController, homeActivity);
-
- assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
-
- // Check that the home app is in portrait
- assertEquals(Configuration.ORIENTATION_PORTRAIT,
- homeActivity.getConfiguration().orientation);
-
- // Home activity won't become top (return to landActivity), so the top rotated record should
- // be cleared.
- mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
- assertFalse(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
- assertFalse(mDefaultDisplay.hasTopFixedRotationLaunchingApp());
- // The transform should keep until the transition is done, so the restored configuration
- // won't be sent to activity and cause unnecessary configuration change.
- assertTrue(homeActivity.hasFixedRotationTransform());
-
- // In real case the transition will be executed from RecentsAnimation#finishAnimation.
- mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(
- homeActivity.token);
- assertFalse(homeActivity.hasFixedRotationTransform());
- }
-
- private ActivityRecord prepareFixedRotationLaunchingAppWithRecentsAnim() {
- final ActivityRecord homeActivity = createHomeActivity();
- homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- // Add a window so it can be animated by the recents.
- final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
- activity.addWindow(win);
- // Assume an activity is launching to different rotation.
- mDefaultDisplay.setFixedRotationLaunchingApp(activity,
- (mDefaultDisplay.getRotation() + 1) % 4);
-
- assertTrue(activity.hasFixedRotationTransform());
- assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(activity));
-
- // Before the transition is done, the recents animation is triggered.
- initializeRecentsAnimationController(mController, homeActivity);
- assertFalse(homeActivity.hasFixedRotationTransform());
- assertTrue(mController.isAnimatingTask(activity.getTask()));
-
- return activity;
- }
-
- @Test
- public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() {
- final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim();
-
- // Simulate giving up the swipe up gesture to keep the original activity as top.
- mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
- // The rotation transform should be cleared after updating orientation with display.
- assertTopFixedRotationLaunchingAppCleared(activity);
-
- // Simulate swiping up recents (home) in different rotation.
- final ActivityRecord home = mDefaultDisplay.getDefaultTaskDisplayArea().getHomeActivity();
- startRecentsInDifferentRotation(home);
-
- // If the recents activity becomes the top running activity (e.g. the original top activity
- // is either finishing or moved to back during recents animation), the display orientation
- // will be determined by it so the fixed rotation must be cleared.
- activity.finishing = true;
- mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
- assertTopFixedRotationLaunchingAppCleared(home);
-
- startRecentsInDifferentRotation(home);
- // Assume recents activity becomes invisible for some reason (e.g. screen off).
- home.setVisible(false);
- mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
- // Although there won't be a transition finish callback, the fixed rotation must be cleared.
- assertTopFixedRotationLaunchingAppCleared(home);
- }
-
- @Test
- public void testKeepFixedRotationWhenMovingRecentsToTop() {
- final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim();
- // Assume a transition animation has started running before recents animation. Then the
- // activity will receive onAnimationFinished that notifies app transition finished when
- // removing the recents animation of task.
- activity.getTask().getAnimationSources().add(activity);
-
- // Simulate swiping to home/recents before the transition is done.
- mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
- // The rotation transform should be preserved. In real case, it will be cleared by the next
- // move-to-top transition.
- assertTrue(activity.hasFixedRotationTransform());
- }
-
- @Test
- public void testCheckRotationAfterCleanup() {
- mWm.setRecentsAnimationController(mController);
- spyOn(mDisplayContent.mFixedRotationTransitionListener);
- final ActivityRecord recents = mock(ActivityRecord.class);
- recents.setOverrideOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
- doReturn(ORIENTATION_PORTRAIT).when(recents)
- .getRequestedConfigurationOrientation(anyBoolean());
- mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recents);
-
- // Rotation update is skipped while the recents animation is running.
- final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
- final int topOrientation = DisplayContentTests.getRotatedOrientation(mDefaultDisplay);
- assertFalse(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */));
- assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
- final int prevRotation = mDisplayContent.getRotation();
- mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-
- // In real case, it is called from RecentsAnimation#finishAnimation -> continueWindowLayout
- // -> handleAppTransitionReady -> add FINISH_LAYOUT_REDO_CONFIG, and DisplayContent#
- // applySurfaceChangesTransaction will call updateOrientation for FINISH_LAYOUT_REDO_CONFIG.
- assertTrue(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */));
- // The display should be updated to the changed orientation after the animation is finished.
- assertNotEquals(displayRotation.getRotation(), prevRotation);
- }
-
- @Test
- public void testWallpaperHasFixedRotationApplied() {
- makeDisplayPortrait(mDefaultDisplay);
- unblockDisplayRotation(mDefaultDisplay);
- mWm.setRecentsAnimationController(mController);
-
- // Create a portrait home activity, a wallpaper and a landscape activity displayed on top.
- final ActivityRecord homeActivity = createHomeActivity();
- homeActivity.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-
- final WindowState homeWindow = createWindow(null, TYPE_BASE_APPLICATION, homeActivity,
- "homeWindow");
- makeWindowVisible(homeWindow);
- homeActivity.addWindow(homeWindow);
- homeWindow.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
-
- // Landscape application
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState applicationWindow = createWindow(null, TYPE_BASE_APPLICATION, activity,
- "applicationWindow");
- activity.addWindow(applicationWindow);
- activity.setOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
-
- // Wallpaper
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
- final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
- "wallpaperWindow");
-
- // Make sure the landscape activity is on top and the display is in landscape
- activity.moveFocusableActivityToTop("test");
- mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
- mDefaultDisplay.getRotation());
-
- spyOn(mDefaultDisplay.mWallpaperController);
- doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
-
- // Start the recents animation
- initializeRecentsAnimationController(mController, homeActivity);
-
- mDefaultDisplay.mWallpaperController.adjustWallpaperWindows();
-
- // Check preconditions
- ArrayList<WallpaperWindowToken> wallpapers = new ArrayList<>(1);
- mDefaultDisplay.forAllWallpaperWindows(wallpapers::add);
-
- Truth.assertThat(wallpapers).hasSize(1);
- Truth.assertThat(wallpapers.get(0).getTopChild()).isEqualTo(wallpaperWindow);
-
- // Actual check
- assertEquals(Configuration.ORIENTATION_PORTRAIT,
- wallpapers.get(0).getConfiguration().orientation);
-
- mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
- // The transform state should keep because we expect to listen the signal from the
- // transition executed by moving the task to front.
- assertTrue(homeActivity.hasFixedRotationTransform());
- assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
-
- mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(
- homeActivity.token);
- // Wallpaper's transform state should be cleared with home.
- assertFalse(homeActivity.hasFixedRotationTransform());
- assertFalse(wallpaperWindowToken.hasFixedRotationTransform());
- }
-
- @Test
- public void testIsAnimatingByRecents() {
- final ActivityRecord homeActivity = createHomeActivity();
- final Task rootTask = createTask(mDefaultDisplay);
- final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
- final Task leafTask = createTaskInRootTask(childTask, 0 /* userId */);
- spyOn(leafTask);
- doReturn(true).when(leafTask).isVisible();
-
- initializeRecentsAnimationController(mController, homeActivity);
-
- // Verify RecentsAnimationController will animate visible leaf task by default.
- verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), any());
- assertTrue(leafTask.isAnimatingByRecents());
-
- // Make sure isAnimatingByRecents will also return true when it called by the parent task.
- assertTrue(rootTask.isAnimatingByRecents());
- assertTrue(childTask.isAnimatingByRecents());
- }
-
- @Test
- public void testRestoreNavBarWhenEnteringRecents_expectAnimation() {
- setupForShouldAttachNavBarDuringTransition();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final ActivityRecord homeActivity = createHomeActivity();
- initializeRecentsAnimationController(mController, homeActivity);
-
- final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
- final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
-
- verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
- eq(mDefaultDisplay.mDisplayId), eq(false));
- verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
- verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
- assertTrue(mController.isNavigationBarAttachedToApp());
-
- mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
- verify(mController).restoreNavigationBarFromApp(eq(true));
- verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
- eq(mDefaultDisplay.mDisplayId), eq(true));
- verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
- assertFalse(mController.isNavigationBarAttachedToApp());
- assertTrue(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
- }
-
- @Test
- public void testRestoreNavBarWhenBackToApp_expectNoAnimation() {
- setupForShouldAttachNavBarDuringTransition();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final ActivityRecord homeActivity = createHomeActivity();
- initializeRecentsAnimationController(mController, homeActivity);
-
- final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
- final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
-
- verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
- eq(mDefaultDisplay.mDisplayId), eq(false));
- verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
- verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
- assertTrue(mController.isNavigationBarAttachedToApp());
-
- final WindowContainer parent = navToken.getParent();
-
- mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
- verify(mController).restoreNavigationBarFromApp(eq(false));
- verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
- eq(mDefaultDisplay.mDisplayId), eq(true));
- verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
- verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
- assertFalse(mController.isNavigationBarAttachedToApp());
- assertFalse(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
- }
-
- @Test
- public void testAddTaskToTargets_expectAnimation() {
- setupForShouldAttachNavBarDuringTransition();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final ActivityRecord homeActivity = createHomeActivity();
- initializeRecentsAnimationController(mController, homeActivity);
-
- final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
- final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
-
- verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
- eq(mDefaultDisplay.mDisplayId), eq(false));
- verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
- verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
- assertTrue(mController.isNavigationBarAttachedToApp());
-
- final WindowContainer parent = navToken.getParent();
-
- mController.addTaskToTargets(createTask(mDefaultDisplay), (type, anim) -> {});
- mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
- verify(mController).restoreNavigationBarFromApp(eq(true));
- verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
- eq(mDefaultDisplay.mDisplayId), eq(true));
- verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
- assertFalse(mController.isNavigationBarAttachedToApp());
- assertTrue(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
- }
-
- @Test
- public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
- setupForShouldAttachNavBarDuringTransition();
- AsyncRotationController mockController =
- mock(AsyncRotationController.class);
- doReturn(mockController).when(mDefaultDisplay).getAsyncRotationController();
- final ActivityRecord homeActivity = createHomeActivity();
- initializeRecentsAnimationController(mController, homeActivity);
- assertFalse(mController.isNavigationBarAttachedToApp());
- }
-
- @Test
- public void testAttachNavBarInSplitScreenMode() {
- setupForShouldAttachNavBarDuringTransition();
- TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
- final ActivityRecord primary = createActivityRecordWithParentTask(
- organizer.createTaskToPrimary(true));
- final ActivityRecord secondary = createActivityRecordWithParentTask(
- organizer.createTaskToSecondary(true));
- final ActivityRecord homeActivity = createHomeActivity();
- homeActivity.setVisibility(true);
- initializeRecentsAnimationController(mController, homeActivity);
-
- WindowState navWindow = mController.getNavigationBarWindow();
- final WindowToken navToken = navWindow.mToken;
- final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
-
- verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
- eq(mDefaultDisplay.mDisplayId), eq(false));
- verify(navWindow).setSurfaceTranslationY(-secondary.getBounds().top);
- verify(transaction).reparent(navToken.getSurfaceControl(), secondary.getSurfaceControl());
- assertTrue(mController.isNavigationBarAttachedToApp());
- reset(navWindow);
-
- mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
- final WindowContainer parent = navToken.getParent();
- verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
- eq(mDefaultDisplay.mDisplayId), eq(true));
- verify(navWindow).setSurfaceTranslationY(0);
- verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
- verify(mController).restoreNavigationBarFromApp(eq(false));
- assertFalse(mController.isNavigationBarAttachedToApp());
- }
-
- @Test
- public void testCleanupAnimation_expectExitAnimationDone() {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord homeActivity = createHomeActivity();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
-
- initializeRecentsAnimationController(mController, homeActivity);
- mController.startAnimation();
-
- spyOn(win1);
- spyOn(win1.mWinAnimator);
- // Simulate when the window is exiting and cleanupAnimation invoked
- // (e.g. screen off during RecentsAnimation animating), will expect the window receives
- // onExitAnimationDone to destroy the surface when the removal is allowed.
- win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
- win1.mHasSurface = true;
- win1.mAnimatingExit = true;
- win1.mRemoveOnExit = true;
- win1.mWindowRemovalAllowed = true;
- mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
- verify(win1).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), any());
- verify(win1).onExitAnimationDone();
- verify(win1).destroySurface(eq(false), eq(false));
- assertFalse(win1.mAnimatingExit);
- assertFalse(win1.mHasSurface);
- }
-
- @Test
- public void testCancelForRotation_ReorderToTop() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
-
- mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
- mController.setWillFinishToHome(true);
- mController.cancelAnimationForDisplayChange();
-
- verify(mMockRunner).onAnimationCanceled(any(), any());
- verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false);
- }
-
- @Test
- public void testCancelForRotation_ReorderToOriginalPosition() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
-
- mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
- mController.setWillFinishToHome(false);
- mController.cancelAnimationForDisplayChange();
-
- verify(mMockRunner).onAnimationCanceled(any(), any());
- verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false);
- }
-
- @Test
- public void testCancelForStartHome() throws Exception {
- mWm.setRecentsAnimationController(mController);
- final ActivityRecord homeActivity = createHomeActivity();
- final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
- activity.addWindow(win1);
-
- initializeRecentsAnimationController(mController, homeActivity);
- mController.setWillFinishToHome(true);
-
- // Verify cancel is called with a snapshot and that we've created an overlay
- spyOn(mWm.mTaskSnapshotController);
- doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
- anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
- mController.cancelAnimationForHomeStart();
- verify(mMockRunner).onAnimationCanceled(any(), any());
-
- // Continue the animation (simulating a call to cleanupScreenshot())
- mController.continueDeferredCancelAnimation();
- verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false);
-
- // Assume home was moved to front so will-be-top callback should not be called.
- homeActivity.moveFocusableActivityToTop("test");
- spyOn(mDefaultDisplay.mFixedRotationTransitionListener);
- mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
- verify(mDefaultDisplay.mFixedRotationTransitionListener, never()).notifyRecentsWillBeTop();
- }
-
- private ActivityRecord createHomeActivity() {
- final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
- .setParentTask(mRootHomeTask)
- .setCreateTask(true)
- .build();
- // Avoid {@link RecentsAnimationController.TaskAnimationAdapter#createRemoteAnimationTarget}
- // returning null when calling {@link RecentsAnimationController#createAppAnimations}.
- homeActivity.setVisibility(true);
- return homeActivity;
- }
-
- private void startRecentsInDifferentRotation(ActivityRecord recentsActivity) {
- final DisplayContent displayContent = recentsActivity.mDisplayContent;
- displayContent.setFixedRotationLaunchingApp(recentsActivity,
- (displayContent.getRotation() + 1) % 4);
- mController = new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
- displayContent.getDisplayId());
- initializeRecentsAnimationController(mController, recentsActivity);
- assertTrue(recentsActivity.hasFixedRotationTransform());
- }
-
- private static void assertTopFixedRotationLaunchingAppCleared(ActivityRecord activity) {
- assertFalse(activity.hasFixedRotationTransform());
- assertFalse(activity.mDisplayContent.hasTopFixedRotationLaunchingApp());
- }
-
- private void setupForShouldAttachNavBarDuringTransition() {
- final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar"));
- mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
- mWm.setRecentsAnimationController(mController);
- doReturn(navBar).when(mController).getNavigationBarWindow();
- final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy());
- doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy();
- doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition();
- }
-
- private static void initializeRecentsAnimationController(RecentsAnimationController controller,
- ActivityRecord activity) {
- controller.initialize(activity.getActivityType(), new SparseBooleanArray(), activity);
- }
-
- private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
- verify(binder, atLeast(0)).asBinder();
- verifyNoMoreInteractions(binder);
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index f93ffb83178f..6f7d0dced484 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -36,7 +36,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
@@ -728,70 +727,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
}
}
- @Test
- public void testNonAppTarget_notSendNavBar_controlledByRecents() throws Exception {
- final RecentsAnimationController mockController =
- mock(RecentsAnimationController.class);
- doReturn(mockController).when(mWm).getRecentsAnimationController();
- final int transit = TRANSIT_OLD_TASK_OPEN;
- setupForNonAppTargetNavBar(transit, true);
-
- final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
- ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
- verify(mMockRunner).onAnimationStart(eq(transit),
- any(), any(), nonAppsCaptor.capture(), any());
- for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
- if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
- fail("Non-app animation target must not contain navbar");
- }
- }
- }
-
- @android.platform.test.annotations.RequiresFlagsDisabled(
- com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
- @SetupWindows(addWindows = W_INPUT_METHOD)
- @Test
- public void testLaunchRemoteAnimationWithoutImeBehind() {
- final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
- final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
-
- // Simulating win1 has shown IME and being IME layering/input target
- mDisplayContent.setImeLayeringTarget(win1);
- mDisplayContent.setImeInputTarget(win1);
- mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
- spyOn(mDisplayContent);
- mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
- makeWindowVisibleAndDrawn(mImeWindow);
- assertTrue(mImeWindow.isOnScreen());
- assertFalse(mImeWindow.isParentWindowHidden());
-
- try {
- // Simulating now win1 is being covered by the lockscreen which has no surface,
- // and then launching an activity win2 with the remote animation
- win1.mHasSurface = false;
- win1.mActivityRecord.setVisibility(false);
- mDisplayContent.mOpeningApps.add(win2.mActivityRecord);
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
- win2.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null, false).mAdapter;
- adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
- mFinishedCallback);
-
- mDisplayContent.applySurfaceChangesTransaction();
- mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
- mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
- verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
- any(), any(), any(), any());
- // Verify the IME window won't apply surface change transaction with forAllImeWindows
- verify(mDisplayContent, never()).forAllImeWindows(any(), eq(true));
- } catch (Exception e) {
- // no-op
- } finally {
- mDisplayContent.mOpeningApps.clear();
- }
- }
-
private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index e019a416c069..6adf0fe15ba8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1260,68 +1260,38 @@ public class RootTaskTests extends WindowTestsBase {
@Test
public void testShouldSleepActivities() {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ task.mDisplayContent = mock(DisplayContent.class);
// When focused activity and keyguard is going away, we should not sleep regardless
// of the display state, but keyguard-going-away should only take effects on default
- // display since there is no keyguard on secondary displays (yet).
- verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+ // display because the keyguard-going-away state of secondary displays are already the
+ // same as default display.
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
- verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */);
// When not the focused root task, defer to display sleeping state.
- verifyShouldSleepActivities(false /* focusedRootTask */, true /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, false /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
// If keyguard is going away, defer to the display sleeping state.
- verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
- verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
}
- @Test
- public void testRootTaskOrderChangedOnRemoveRootTask() {
- final Task task = new TaskBuilder(mSupervisor).build();
- RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener();
- mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener);
- try {
- mDefaultTaskDisplayArea.removeRootTask(task);
- } finally {
- mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(listener);
- }
- assertTrue(listener.mChanged);
- }
-
- @Test
- public void testRootTaskOrderChangedOnAddPositionRootTask() {
- final Task task = new TaskBuilder(mSupervisor).build();
- mDefaultTaskDisplayArea.removeRootTask(task);
-
- RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener();
- mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener);
- try {
- task.mReparenting = true;
- mDefaultTaskDisplayArea.addChild(task, 0);
- } finally {
- mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(listener);
- }
- assertTrue(listener.mChanged);
- }
+ private static void verifyShouldSleepActivities(Task task, boolean isVisibleTask,
+ boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
+ boolean expected) {
+ final DisplayContent display = task.mDisplayContent;
+ display.isDefaultDisplay = isDefaultDisplay;
+ doReturn(keyguardGoingAway).when(display).isKeyguardGoingAway();
+ doReturn(displaySleeping).when(display).isSleeping();
+ doReturn(isVisibleTask).when(task).shouldBeVisible(null /* starting */);
- @Test
- public void testRootTaskOrderChangedOnPositionRootTask() {
- RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener();
- try {
- final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener);
- mDefaultTaskDisplayArea.positionChildAt(POSITION_BOTTOM, fullscreenRootTask1,
- false /*includingParents*/);
- } finally {
- mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(listener);
- }
- assertTrue(listener.mChanged);
+ assertEquals(expected, task.shouldSleepActivities());
}
@Test
@@ -1451,11 +1421,6 @@ public class RootTaskTests extends WindowTestsBase {
anyBoolean());
}
- private boolean isAssistantOnTop() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_assistantOnTopOfDream);
- }
-
private void verifyShouldSleepActivities(boolean focusedRootTask,
boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
boolean expected) {
@@ -1471,14 +1436,4 @@ public class RootTaskTests extends WindowTestsBase {
assertEquals(expected, task.shouldSleepActivities());
}
-
- private static class RootTaskOrderChangedListener
- implements TaskDisplayArea.OnRootTaskOrderChangedListener {
- public boolean mChanged = false;
-
- @Override
- public void onRootTaskOrderChanged(Task rootTask) {
- mChanged = true;
- }
- }
}
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 5bb437827dae..f74340113a04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -613,7 +613,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertFalse(mActivity.mDisplayContent.shouldImeAttachedToApp());
// Recompute the natural configuration without resolving size compat configuration.
- mActivity.clearSizeCompatMode();
+ mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
mActivity.onConfigurationChanged(mTask.getConfiguration());
// It should keep non-attachable because the resolved bounds will be computed according to
// the aspect ratio that won't match its parent bounds.
@@ -706,7 +706,7 @@ public class SizeCompatTests extends WindowTestsBase {
/ originalBounds.width()));
// Recompute the natural configuration in the new display.
- mActivity.clearSizeCompatMode();
+ mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
mActivity.ensureActivityConfiguration();
// Because the display cannot rotate, the portrait activity will fit the short side of
// display with keeping portrait bounds [200, 0 - 700, 1000] in center.
@@ -1482,7 +1482,7 @@ public class SizeCompatTests extends WindowTestsBase {
// After changing the orientation to portrait the override should be applied.
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
// The per-package override forces the activity into a 3:2 aspect ratio
assertEquals(1200, activity.getBounds().height());
@@ -1511,7 +1511,7 @@ public class SizeCompatTests extends WindowTestsBase {
// After changing the orientation to portrait the override should be applied.
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
// The per-package override forces the activity into a 3:2 aspect ratio
assertEquals(1200, activity.getBounds().height());
@@ -1538,7 +1538,7 @@ public class SizeCompatTests extends WindowTestsBase {
// After changing the orientation to landscape the override shouldn't be applied.
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
// The per-package override should have no effect
assertEquals(1200, activity.getBounds().height());
@@ -3054,7 +3054,10 @@ public class SizeCompatTests extends WindowTestsBase {
false /* deferPause */);
// App still in size compat, and the bounds don't change.
- verify(mActivity, never()).clearSizeCompatMode();
+ final AppCompatSizeCompatModePolicy scmPolicy = mActivity.mAppCompatController
+ .getAppCompatSizeCompatModePolicy();
+ spyOn(scmPolicy);
+ verify(scmPolicy, never()).clearSizeCompatMode();
assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
assertDownScaled();
@@ -3896,7 +3899,7 @@ public class SizeCompatTests extends WindowTestsBase {
private void recomputeNaturalConfigurationOfUnresizableActivity() {
// Recompute the natural configuration of the non-resizable activity and the split screen.
- mActivity.clearSizeCompatMode();
+ mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
// Draw letterbox.
mActivity.setVisible(false);
@@ -4827,7 +4830,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(origDensity, mActivity.getConfiguration().densityDpi);
// Activity should exit size compat with new density.
- mActivity.clearSizeCompatMode();
+ mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
assertFitted();
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
@@ -5013,7 +5016,7 @@ public class SizeCompatTests extends WindowTestsBase {
activity.setRequestedOrientation(screenOrientation);
}
// Make sure to use the provided configuration to construct the size compat fields.
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
activity.ensureActivityConfiguration();
// Make sure the display configuration reflects the change of activity.
if (activity.mDisplayContent.updateOrientation()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6c8a7ac0c613..9981a4dd9fce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -23,7 +23,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -96,18 +95,18 @@ public class SurfaceAnimatorTest extends WindowTestsBase {
@Test
public void testRunAnimation() {
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
- ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION);
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
OnAnimationFinishedCallback.class);
assertAnimating(mAnimatable);
verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
- verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_RECENTS),
+ verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
callbackCaptor.capture());
- callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_RECENTS, mSpec);
+ callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
assertNotAnimating(mAnimatable);
assertTrue(mAnimatable.mFinishedCallbackCalled);
- assertEquals(ANIMATION_TYPE_RECENTS, mAnimatable.mFinishedAnimationType);
+ assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
verify(mTransaction).remove(eq(mAnimatable.mLeash));
// TODO: Verify reparenting once we use mPendingTransaction to reparent it back
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 56fca31afa37..52a80b01971d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1251,7 +1251,7 @@ public class TransitionTests extends WindowTestsBase {
final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN);
app.mTransitionController.requestStartTransition(transition, app.getTask(),
null /* remoteTransition */, null /* displayChange */);
- app.mTransitionController.collectExistenceChange(app.getTask());
+ transition.collectExistenceChange(app.getTask());
mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
@@ -1416,7 +1416,8 @@ public class TransitionTests extends WindowTestsBase {
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
- openTransition.finishTransition();
+ final ActionChain chain = ActionChain.testFinish(null);
+ openTransition.finishTransition(chain);
// We finished the openTransition. Even though activity1 is visibleRequested=false, since
// the closeTransition animation hasn't played yet, make sure that we didn't commit
@@ -1429,7 +1430,7 @@ public class TransitionTests extends WindowTestsBase {
// normally.
mWm.mSyncEngine.abort(closeTransition.getSyncId());
- closeTransition.finishTransition();
+ closeTransition.finishTransition(chain);
assertFalse(activity1.isVisible());
assertTrue(activity2.isVisible());
@@ -1449,7 +1450,7 @@ public class TransitionTests extends WindowTestsBase {
activity1.setState(ActivityRecord.State.INITIALIZING, "test");
activity1.mLaunchTaskBehind = true;
mWm.mSyncEngine.abort(noChangeTransition.getSyncId());
- noChangeTransition.finishTransition();
+ noChangeTransition.finishTransition(chain);
assertTrue(activity1.mLaunchTaskBehind);
}
@@ -1468,7 +1469,7 @@ public class TransitionTests extends WindowTestsBase {
// We didn't call abort on the transition itself, so it will still run onTransactionReady
// normally.
mWm.mSyncEngine.abort(transition1.getSyncId());
- transition1.finishTransition();
+ transition1.finishTransition(ActionChain.testFinish(transition1));
verify(transitionEndedListener).run();
@@ -1530,7 +1531,7 @@ public class TransitionTests extends WindowTestsBase {
verify(taskSnapshotController, times(1)).recordSnapshot(eq(task2));
- controller.finishTransition(openTransition);
+ controller.finishTransition(ActionChain.testFinish(openTransition));
// We are now going to simulate closing task1 to return back to (open) task2.
final Transition closeTransition = createTestTransition(TRANSIT_CLOSE, controller);
@@ -1595,7 +1596,7 @@ public class TransitionTests extends WindowTestsBase {
doReturn(true).when(task1).isTranslucentForTransition();
assertFalse(controller.canApplyDim(task1));
- controller.finishTransition(closeTransition);
+ controller.finishTransition(ActionChain.testFinish(closeTransition));
assertTrue(wasInFinishingTransition[0]);
assertFalse(calledListenerOnOtherDisplay[0]);
assertNull(controller.mFinishingTransition);
@@ -1651,7 +1652,7 @@ public class TransitionTests extends WindowTestsBase {
// to avoid the latency to resume the current top, i.e. appB.
assertTrue(controller.isTransientVisible(taskRecent));
// The recent is paused after the transient transition is finished.
- controller.finishTransition(transition);
+ controller.finishTransition(ActionChain.testFinish(transition));
assertFalse(controller.isTransientVisible(taskRecent));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index fa28d117c5bc..7a440e676b39 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -323,7 +323,10 @@ public class TransparentPolicyTest extends WindowTestsBase {
ta.launchTransparentActivityInTask();
a.assertNotNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
- a.applyToTopActivity(ActivityRecord::clearSizeCompatMode);
+ a.applyToTopActivity((top) -> {
+ top.mAppCompatController.getAppCompatSizeCompatModePolicy()
+ .clearSizeCompatMode();
+ });
a.assertNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
});
});
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index c65b76efd614..9602ae29604f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -37,7 +37,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -276,9 +275,8 @@ public class WallpaperControllerTests extends WindowTestsBase {
final DisplayContent dc = mDisplayContent;
final WindowState homeWin = createWallpaperTargetWindow(dc);
final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
- final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
- doReturn(true).when(recentsController).isWallpaperVisible(eq(appWin));
- mWm.setRecentsAnimationController(recentsController);
+ appWin.mAttrs.flags |= FLAG_SHOW_WALLPAPER;
+ makeWindowVisible(appWin);
dc.mWallpaperController.adjustWallpaperWindows();
assertEquals(appWin, dc.mWallpaperController.getWallpaperTarget());
@@ -354,46 +352,6 @@ public class WallpaperControllerTests extends WindowTestsBase {
}
@Test
- public void testFixedRotationRecentsAnimatingTask() {
- final WindowState wallpaperWindow = createWallpaperWindow(mDisplayContent);
- final WallpaperWindowToken wallpaperToken = wallpaperWindow.mToken.asWallpaperToken();
- final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
- makeWindowVisible(appWin);
- final ActivityRecord r = appWin.mActivityRecord;
- final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
- doReturn(true).when(recentsController).isWallpaperVisible(eq(appWin));
- mWm.setRecentsAnimationController(recentsController);
-
- r.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
- mDisplayContent.mDisplayFrames, mDisplayContent.getConfiguration());
- // Invisible requested activity should not share its rotation transform.
- r.setVisibleRequested(false);
- mDisplayContent.mWallpaperController.adjustWallpaperWindows();
- assertFalse(wallpaperToken.hasFixedRotationTransform());
-
- // Wallpaper should link the transform of its target.
- r.setVisibleRequested(true);
- mDisplayContent.mWallpaperController.adjustWallpaperWindows();
- assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
- assertTrue(r.hasFixedRotationTransform());
- assertTrue(wallpaperToken.hasFixedRotationTransform());
-
- // The case with shell transition.
- registerTestTransitionPlayer();
- final Transition t = r.mTransitionController.createTransition(TRANSIT_OPEN);
- final ActivityRecord recents = mock(ActivityRecord.class);
- t.collect(r.getTask());
- r.mTransitionController.setTransientLaunch(recents, r.getTask());
- // The activity in restore-below task should not be the target if keyguard is not locked.
- mDisplayContent.mWallpaperController.adjustWallpaperWindows();
- assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
- // The activity in restore-below task should not be the target if keyguard is occluded.
- doReturn(true).when(mDisplayContent).isKeyguardLocked();
- mDisplayContent.mWallpaperController.adjustWallpaperWindows();
- assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
- }
-
- @Test
public void testWallpaperReportConfigChange() {
final WindowState wallpaperWindow = createWallpaperWindow(mDisplayContent);
createWallpaperTargetWindow(mDisplayContent);
@@ -449,7 +407,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
token.finishSync(t, token.getSyncGroup(), false /* cancel */);
transit.onTransactionReady(transit.getSyncId(), t);
- dc.mTransitionController.finishTransition(transit);
+ dc.mTransitionController.finishTransition(ActionChain.testFinish(transit));
assertFalse(wallpaperWindow.isVisible());
assertFalse(token.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 22def515a98e..72935cb546d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -62,8 +62,6 @@ public class WindowContainerTraversalTests extends WindowTestsBase {
verify(c).accept(eq(mImeWindow));
}
- @android.platform.test.annotations.RequiresFlagsEnabled(
- com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
@SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testTraverseImeRegardlessOfImeTarget() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 0cb22ad47355..9bad2ec2ca2a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -44,7 +44,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.Manifest;
@@ -197,21 +196,6 @@ public class WindowProcessControllerTests extends WindowTestsBase {
}
@Test
- public void testSetRunningBothAnimations() {
- mWpc.setRunningRemoteAnimation(true);
- mWpc.setRunningRecentsAnimation(true);
-
- mWpc.setRunningRecentsAnimation(false);
- mWpc.setRunningRemoteAnimation(false);
- waitHandlerIdle(mAtm.mH);
-
- InOrder orderVerifier = Mockito.inOrder(mMockListener);
- orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(true));
- orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(false));
- orderVerifier.verifyNoMoreInteractions();
- }
-
- @Test
public void testConfigurationForSecondaryScreenDisplayArea() {
// By default, the process should not listen to any display area.
assertNull(mWpc.getDisplayArea());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index bcf4ebcd9740..a215c0a80b46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -2131,7 +2131,7 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
public void finish() {
- mController.finishTransition(mLastTransit);
+ mController.finishTransition(ActionChain.testFinish(mLastTransit));
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 973ab846fa4a..88ce3a6f5bf8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -40,23 +40,16 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.os.Binder;
import android.platform.test.annotations.Presubmit;
-import android.util.SparseBooleanArray;
-import android.view.IRecentsAnimationRunner;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.ScreenCapture;
@@ -410,30 +403,6 @@ public class ZOrderingTests extends WindowTestsBase {
}
@Test
- public void testAssignWindowLayers_ForImeOnAppWithRecentsAnimating() {
- final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
- mAppWindow.mActivityRecord, "imeAppTarget");
- mDisplayContent.setImeInputTarget(imeAppTarget);
- mDisplayContent.setImeLayeringTarget(imeAppTarget);
- mDisplayContent.setImeControlTarget(imeAppTarget);
- mDisplayContent.updateImeParent();
-
- // Simulate the ime layering target task is animating with recents animation.
- final Task imeAppTargetTask = imeAppTarget.getTask();
- final SurfaceAnimator imeTargetTaskAnimator = imeAppTargetTask.mSurfaceAnimator;
- spyOn(imeTargetTaskAnimator);
- doReturn(ANIMATION_TYPE_RECENTS).when(imeTargetTaskAnimator).getAnimationType();
- doReturn(true).when(imeTargetTaskAnimator).isAnimating();
-
- mDisplayContent.assignChildLayers(mTransaction);
-
- // Ime should on top of the application window when in recents animation and keep
- // attached on app.
- assertTrue(mDisplayContent.shouldImeAttachedToApp());
- assertWindowHigher(mImeWindow, imeAppTarget);
- }
-
- @Test
public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() {
final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
mAppWindow.mActivityRecord, "imeAppTarget");
@@ -493,43 +462,6 @@ public class ZOrderingTests extends WindowTestsBase {
}
@Test
- public void testAttachNavBarWhenEnteringRecents_expectNavBarHigherThanIme() {
- // create RecentsAnimationController
- IRecentsAnimationRunner mockRunner = mock(IRecentsAnimationRunner.class);
- when(mockRunner.asBinder()).thenReturn(new Binder());
- final int displayId = mDisplayContent.getDisplayId();
- RecentsAnimationController controller = new RecentsAnimationController(
- mWm, mockRunner, null, displayId);
- spyOn(controller);
- doReturn(mNavBarWindow).when(controller).getNavigationBarWindow();
- mWm.setRecentsAnimationController(controller);
-
- // set ime visible
- spyOn(mDisplayContent.mInputMethodWindow);
- doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
-
- DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
- spyOn(policy);
- doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
-
- // create home activity
- Task rootHomeTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
- final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
- .setParentTask(rootHomeTask)
- .setCreateTask(true)
- .build();
- homeActivity.setVisibility(true);
-
- // start recent animation
- controller.initialize(homeActivity.getActivityType(), new SparseBooleanArray(),
- homeActivity);
-
- mDisplayContent.assignChildLayers(mTransaction);
- assertZOrderGreaterThan(mTransaction, mNavBarWindow.mToken.getSurfaceControl(),
- mDisplayContent.getImeContainer().getSurfaceControl());
- }
-
- @Test
public void testPopupWindowAndParentIsImeTarget_expectHigherThanIme_inMultiWindow() {
// Simulate the app window is in multi windowing mode and being IME target
mAppWindow.getConfiguration().windowConfiguration.setWindowingMode(
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index e6fe406dd8e5..83dac184fc5f 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -175,8 +175,15 @@ public final class PhoneAccount implements Parcelable {
* <p>
* The call recording tone is a 1400 hz tone which repeats every 15 seconds while recording is
* in progress.
+ *
+ * @deprecated this API was only intended to prevent call recording via the microphone by an app
+ * while in a phone call. Audio policies no longer make this possible. Further, this API was
+ * never actually used. Call recording solutions integrated in an OEM dialer app must use
+ * appropriate recording signals to inform the caller/callee of the recording.
* @hide
*/
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ @Deprecated
@SystemApi
public static final String EXTRA_PLAY_CALL_RECORDING_TONE =
"android.telecom.extra.PLAY_CALL_RECORDING_TONE";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index afd5720d9264..1ba496df5005 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -420,6 +420,7 @@ public class CarrierConfigManager {
* <p>
* Note: This requires the Telephony config_supports_telephony_audio_device overlay to be true
* in order to work.
+ * @deprecated this functionality was never used and is no longer supported.
* @hide
*/
public static final String KEY_PLAY_CALL_RECORDING_TONE_BOOL = "play_call_recording_tone_bool";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3e8b3268ae4c..7481daa8e396 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1777,8 +1777,8 @@ public class TelephonyManager {
/**
* Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE}
- * to indicate user to decide whether current SIM should be preferred for all
- * data / voice / sms. {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
+ * to indicate the current SIM should be preferred for all data / voice / sms.
+ * {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
* which subscription should be the default subscription.
* @hide
*/
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
index 8040610c485b..cfc818b6c0e9 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
@@ -31,8 +31,7 @@ import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButton3ButtonLandscape :
CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
- // TODO: Missing CUJ (b/300078127)
- @ExpectedScenarios(["ENTIRE_TRACE"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
index aacccf4e680c..6bf32a8e2083 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
@@ -31,8 +31,7 @@ import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButton3ButtonPortrait :
CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
- // TODO: Missing CUJ (b/300078127)
- @ExpectedScenarios(["ENTIRE_TRACE"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
index 74ee46093f6e..4b6ab773f15e 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
@@ -31,8 +31,7 @@ import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButtonGesturalNavLandscape :
CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
- // TODO: Missing CUJ (b/300078127)
- @ExpectedScenarios(["ENTIRE_TRACE"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
index 57463c33c1fa..7cc9db027e1f 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
@@ -31,8 +31,7 @@ import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButtonGesturalNavPortrait :
CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
- // TODO: Missing CUJ (b/300078127)
- @ExpectedScenarios(["ENTIRE_TRACE"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 05a68e9649d4..6db5f8277e52 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -798,6 +798,38 @@ public class PerfettoProtoLogImplTest {
.isEqualTo("My Test Debug Log Message true");
}
+ @Test
+ public void usesDefaultLogFromLevel() throws IOException {
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(LogLevel.WARN).build();
+ try {
+ traceMonitor.start();
+ mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "This message should not be logged");
+ mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP,
+ "This message should logged %d", 123);
+ mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP,
+ "This message should also be logged %d", 567);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(2);
+
+ Truth.assertThat(protolog.messages.get(0).getLevel())
+ .isEqualTo(LogLevel.WARN);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("This message should logged 123");
+
+ Truth.assertThat(protolog.messages.get(1).getLevel())
+ .isEqualTo(LogLevel.ERROR);
+ Truth.assertThat(protolog.messages.get(1).getMessage())
+ .isEqualTo("This message should also be logged 567");
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
index e3ec62d5b5a6..aba6722c0813 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -41,14 +41,14 @@ import java.io.PrintWriter;
public class ProtoLogCommandHandlerTest {
@Mock
- ProtoLogService mProtoLogService;
+ ProtoLogConfigurationService mProtoLogConfigurationService;
@Mock
PrintWriter mPrintWriter;
@Test
public void printsHelpForAllAvailableCommands() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
cmdHandler.onHelp();
validateOnHelpPrinted();
@@ -57,7 +57,7 @@ public class ProtoLogCommandHandlerTest {
@Test
public void printsHelpIfCommandIsNull() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
cmdHandler.onCommand(null);
validateOnHelpPrinted();
@@ -65,13 +65,13 @@ public class ProtoLogCommandHandlerTest {
@Test
public void handlesGroupListCommand() {
- Mockito.when(mProtoLogService.getGroups())
+ Mockito.when(mProtoLogConfigurationService.getGroups())
.thenReturn(new String[] {"MY_TEST_GROUP", "MY_OTHER_GROUP"});
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups", "list" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "list" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("MY_TEST_GROUP"));
@@ -82,10 +82,10 @@ public class ProtoLogCommandHandlerTest {
@Test
public void handlesIncompleteGroupsCommand() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("Incomplete command"));
@@ -93,13 +93,14 @@ public class ProtoLogCommandHandlerTest {
@Test
public void handlesGroupStatusCommand() {
- Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {"MY_GROUP"});
- Mockito.when(mProtoLogService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
+ Mockito.when(mProtoLogConfigurationService.getGroups())
+ .thenReturn(new String[] {"MY_GROUP"});
+ Mockito.when(mProtoLogConfigurationService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups", "status", "MY_GROUP" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("MY_GROUP"));
@@ -109,12 +110,12 @@ public class ProtoLogCommandHandlerTest {
@Test
public void handlesGroupStatusCommandOfUnregisteredGroups() {
- Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {});
+ Mockito.when(mProtoLogConfigurationService.getGroups()).thenReturn(new String[] {});
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups", "status", "MY_GROUP" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("MY_GROUP"));
@@ -125,10 +126,10 @@ public class ProtoLogCommandHandlerTest {
@Test
public void handlesGroupStatusCommandWithNoGroups() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "groups", "status" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("Incomplete command"));
@@ -137,10 +138,10 @@ public class ProtoLogCommandHandlerTest {
@Test
public void handlesIncompleteLogcatCommand() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat" });
Mockito.verify(mPrintWriter, times(1))
.println(contains("Incomplete command"));
@@ -149,50 +150,52 @@ public class ProtoLogCommandHandlerTest {
@Test
public void handlesLogcatEnableCommand() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat", "enable", "MY_GROUP" });
- Mockito.verify(mProtoLogService).enableProtoLogToLogcat("MY_GROUP");
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP");
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err,
new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
- Mockito.verify(mProtoLogService)
+ Mockito.verify(mProtoLogConfigurationService)
.enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
}
@Test
public void handlesLogcatDisableCommand() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat", "disable", "MY_GROUP" });
- Mockito.verify(mProtoLogService).disableProtoLogToLogcat("MY_GROUP");
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP");
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err,
new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
- Mockito.verify(mProtoLogService)
+ Mockito.verify(mProtoLogConfigurationService)
.disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
}
@Test
public void handlesLogcatEnableCommandWithNoGroups() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat", "enable" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "enable" });
Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
}
@Test
public void handlesLogcatDisableCommandWithNoGroups() {
final ProtoLogCommandHandler cmdHandler =
- new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- new String[] { "logcat", "disable" });
+ cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "disable" });
Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
index feac59c702ea..e1bdd777dc5f 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -67,7 +67,7 @@ import java.util.List;
*/
@Presubmit
@RunWith(MockitoJUnitRunner.class)
-public class ProtoLogServiceTest {
+public class ProtoLogConfigurationServiceTest {
private static final String TEST_GROUP = "MY_TEST_GROUP";
private static final String OTHER_TEST_GROUP = "MY_OTHER_TEST_GROUP";
@@ -128,7 +128,7 @@ public class ProtoLogServiceTest {
private File mViewerConfigFile;
- public ProtoLogServiceTest() throws IOException {
+ public ProtoLogConfigurationServiceTest() throws IOException {
}
@Before
@@ -150,10 +150,12 @@ public class ProtoLogServiceTest {
@Test
public void canRegisterClientWithGroupsOnly() throws RemoteException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true));
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
@@ -163,11 +165,13 @@ public class ProtoLogServiceTest {
@Test
public void willDumpViewerConfigOnlyOnceOnTraceStop()
throws RemoteException, InvalidProtocolBufferException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true))
- .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
service.registerClient(mMockClient, args);
service.registerClient(mSecondMockClient, args);
@@ -196,14 +200,15 @@ public class ProtoLogServiceTest {
@Test
public void willDumpViewerConfigOnLastClientDisconnected()
throws RemoteException, FileNotFoundException {
- final ProtoLogService.ViewerConfigFileTracer tracer =
- Mockito.mock(ProtoLogService.ViewerConfigFileTracer.class);
- final ProtoLogService service = new ProtoLogService(tracer);
-
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(
- TEST_GROUP, true))
- .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ final ProtoLogConfigurationService.ViewerConfigFileTracer tracer =
+ Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class);
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer);
+
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
service.registerClient(mMockClient, args);
service.registerClient(mSecondMockClient, args);
@@ -220,10 +225,11 @@ public class ProtoLogServiceTest {
@Test
public void sendEnableLoggingToLogcatToClient() throws RemoteException {
- final var service = new ProtoLogService();
+ final var service = new ProtoLogConfigurationService();
- final var args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ final var args = new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
@@ -236,10 +242,12 @@ public class ProtoLogServiceTest {
@Test
public void sendDisableLoggingToLogcatToClient() throws RemoteException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true));
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
@@ -252,10 +260,12 @@ public class ProtoLogServiceTest {
@Test
public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
@@ -267,14 +277,16 @@ public class ProtoLogServiceTest {
@Test
public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
- final ProtoLogService service = new ProtoLogService();
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
service.enableProtoLogToLogcat(TEST_GROUP);
Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
- final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
- .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
service.registerClient(mMockClient, args);
Mockito.verify(mMockClient).toggleLogcat(eq(true),
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
index 0b619488c49c..9c443324defb 100644
--- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
@@ -23,13 +23,21 @@ import java.io.IOException
import java.util.zip.ZipFile
fun main(args: Array<String>) {
- if (args.size != 2) {
+ if (args.size < 2 || args.size > 3) {
usage()
}
val zipFileName = args[0]
val aidlFileName = args[1]
+ var stable = false
+ if (args.size == 3) {
+ if (args[2] != "--guarantee_stable") {
+ usage()
+ }
+ stable = true
+ }
+
val zipFile: ZipFile
try {
@@ -55,6 +63,9 @@ fun main(args: Array<String>) {
val outFile = File(aidlFileName)
val outWriter = outFile.bufferedWriter()
for (parcelable in parcelables) {
+ if (stable) {
+ outWriter.write("@JavaOnlyStableParcelable ")
+ }
outWriter.write("parcelable ")
outWriter.write(parcelable.replace('/', '.').replace('$', '.'))
outWriter.write(";\n")
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index b0f68f7870ee..f68ae2c7e249 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -98,6 +98,18 @@ public class SharedConnectivityManager {
}
@Override
+ public void onServiceDisconnected() {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onServiceDisconnected());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
if (mCallback != null) {
final long token = Binder.clearCallingIdentity();
@@ -247,13 +259,13 @@ public class SharedConnectivityManager {
mService = null;
synchronized (mProxyDataLock) {
if (!mCallbackProxyCache.isEmpty()) {
- mCallbackProxyCache.keySet().forEach(
- SharedConnectivityClientCallback::onServiceDisconnected);
+ mCallbackProxyCache.values().forEach(
+ SharedConnectivityCallbackProxy::onServiceDisconnected);
mCallbackProxyCache.clear();
}
if (!mProxyMap.isEmpty()) {
- mProxyMap.keySet().forEach(
- SharedConnectivityClientCallback::onServiceDisconnected);
+ mProxyMap.values().forEach(
+ SharedConnectivityCallbackProxy::onServiceDisconnected);
mProxyMap.clear();
}
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
index 521f94367f6f..7b892af3c529 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -32,4 +32,5 @@ interface ISharedConnectivityCallback {
oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status);
oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
oneway void onServiceConnected();
+ oneway void onServiceDisconnected();
}